Listes en compréhension en Python

J’aime principalement deux choses dans le langage Python : la redoutable simplicité de sa syntaxe, et l’incroyable puissance des listes en compréhension, permettant d’effectuer des traitements en une seule ligne imbuvable. Oui, c’est parfaitement contraire au premier point. Je vais donc revenir sur ces listes en compréhensions.

De quoi parle-t-on ?

Les listes en compréhension sont une syntaxe présente dans le langage Python (entre autres) permettant de filtrer un itérable (comme une liste). En gros, cela permet l’écriture d’une boucle for dont la finalité est de créer une liste. Un exemple sera plus parlant.

resultat = []
for i in range(10):
    resultat.append(i*2)

Cette syntaxe classique utilise 3 lignes pour générer la simple liste [0,2,4,6,8,10,12,14,16,18,20]. Voyons maintenant comment écrire cela autrement :

resultat = [i*2 for i in range(10)]

Voila. Rien de plus. Nous arrivons au même résultat avec une écriture bien plus concise. Il est possible de compléter l’exemple précédent :

resultat = []
for i in range(10):
    if(i % 2 == 0):
        resultat.append(i)

On itère i de 0 à 9, et on insère i dans resultat si celui-ci est pair (c’est à dire si le résultat de sa division par 2 est nul).

Voyons maintenant la version en liste en compréhension :

resultat = [i for i in range(10) if i % 2 == 0]

On peut donc, grâce à la version verbeuse de l’expression, isoler les différentes parties :

  • Un itérable, ici range(10), qui va nous servir de donnée de base ;
  • Une valeur, calculée pour chaque passage dans la boucle (il n’est pas obligatoire d’utiliser une valeur provenant de la source) ;
  • Une condition optionnelle, indiquée après l’itérable source.

La puissance des listes en compréhension est incroyable. Pensez que l’itérable source de votre liste en compréhension peut lui aussi être une liste en compréhension !

Expressions génératrices

Si vous ne connaissez pas les générateurs en Python, il s’agit de structures itérables dont la valeur est calculée au moment où on tente d’y accéder, et non pas à l’assignation. Ce qui permet d’itérer sur de très gros volumes de données, mais également d’itérer à l’infini sur une valeur.

>>> def sq(n):
...     print('sq(%d)' % d) # on affiche quelque chose à chaque exécution
...     return n**2
...
>>> l = [sq(i) for i in range(10)]
sq(0)
sq(1)
sq(2)
sq(3)
sq(4)
sq(5)
sq(6)
sq(7)
sq(8)
sq(9)

Comme on le constate, avec une simple liste en compréhention, la fonction sq() est appelée à l’assignation de la liste, car les valeurs sont calculées à ce moment. Ce n’est pas le cas des expressions génératrices.

>>> g = (sq(i) for i in range(10))

Rien n’est affiché. Notre fonction sq() n’est donc pas appelée. Elle le sera à chaque fois qu’on cherchera à accéder à un élément du générateur.

>>> for i in g:
...     print(i)
... 
sq(0)
0
sq(1)
1
sq(2)
4
sq(3)
9
sq(4)
16
sq(5)
25
sq(6)
36
sq(7)
49
sq(8)
64
sq(9)
81

Les lignes « sq(×) » sont le signe que notre fonction sq() est exécutée à ce moment. Et donc, en cas de données lourdes, on ne charge pas tout en mémoire instantanément.

La seule chose qui distingue une expression génératrice d’une liste en compréhension, syntaxiquement parlant, est simplement l’usage de parenthèses autour de l’expression au lieu de crochets.

Sets en compréhension

Enfin, et parce que je préfère évoquer toutes les possibilités de cette syntaxe, sachez qu’il est possible de générer un set (c’est à dire une liste dédoublonnée) à partir d’une liste en compréhension. Il suffit pour cela d’utiliser les accolades au lieu de crochets autour de l’expression.

>>> s = [n % 5 for n in range(10)] # liste en compréhension
>>> s
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
>>> s = {n % 5 for n in range(10)} # set en compréhension, sans doublon
>>> s
{0, 1, 2, 3, 4}

Un exemple ?

La raison profonde pour laquelle j’ai voulu écrire cet article est le besoin récent que j’ai eu de convertir une chaîne binaire en texte, par conversion des octets en nombres décimaux, puis correspondance dans la table ascii. Malgré l’existence de nombreux convertisseurs en ligne (j’en ai moi-même écrit), je me suis dit qu’écrire un convertisseur en une ligne serait amusant, le tout sous les yeux d’une amie. Et donc, voici :

>>> s = '01010000011010010110111001101011011010010110010100100000010100000110100101100101001000000110100101110011001000000111010001101000011001010010000001100010011001010111001101110100'
>>> print(''.join([chr(int(b, 2)) for b in [s[i:i+8] for i in range(0, len(s), 8)]]))
Pinkie Pie is the best

Voilà.

Bon, ok, je vous fais la version longue et commentée :

s = '01010000011010010110111001101011011010010110010100100000010100000110100101100101001000000110100101110011001000000111010001101000011001010010000001100010011001010111001101110100'
conversion = [] # on stocke le résultat dans un tableau, qu’on convertira
                # ensuite en chaîne

# commençons par découper notre chaîne en octets (8 bits)
octets = []
# on doit itérer (taille de la chaîne / 8) arrondi au supérieur (au cas où)
for i in range( 0, len(s), 8 ):
    octets.append(s[i:i+8]) # vivent les slices d’itérable : on découpe
                            # à partir de i caractères jusqu’à 8 de
                            # plus au maximum
# on a maintenant nos octets séparés. Il ne reste plus qu’à les convertir en
# décimaux, puis récupérer la valeur de la table ascii correspondante
for octet in octets:
    octet_dec = int(octet, 2) # pour convertir à partir de la base 2
    conversion.append( chr( octet_dec ) )

print( ''.join( conversion ) ) # ENFIN !

Vous ne trouvez pas que la première version est plus, disons, succinte ?

[edit] Rogdham m’a suggéré une amélioration du convertisseur binaire

Exceptions en Python

Les exceptions sont un mécanisme de développement extrêmement pratique, mais pas forcément clair pour tout le monde. Suite à une petite discussion, voici leur fonctionnement en python3 :

Qu’est-ce qu’une exception ?

Il s’agit d’objets se comportant comme des erreurs de fonctionnement du programme. Toutes les erreurs, en python, sont des exceptions, et sont donc manipulables. Cela sert à pouvoir traiter soi-même les erreurs, au lieu de planter bêtement. Une structure existe pour effectuer ce traitement : son fonctionnement en langue française donnerait ceci :

Bon, effectue ces actions. Si jamais il y a une erreur à l’intérieur, traite-la comme ceci.

En python, ça se traduit par le bloc try … except :

try:
    do_some_actions()
except:
    print('Nous avons une erreur !')

Si le bloc try renvoie une exception (donc une erreur), le bloc except est exécuté. En l’occurrence, on affiche un message, puis on continue le programme. Avec une vraie exception, ça donnerait ça :

try:
    result = 10 / 0 # une division par zéro, c’est paaaaas bien
except:
    print('Nous avons une erreur !')

Cela reste néanmoins un fonctionnement très basique. On ne sait pas, à ce stade, quelle est l’erreur. L’exception étant un objet, pouvoir la manipuler est la base du traitement.

try:
    result = 10 / 0
except Exception, e:
    print('Nous avons une erreur : %s !' % e)

Voici la forme étendue du except : on lui spécifie un type d’exception à gérer, puis un nom de variable, qui contiendra notre exception. L’objet Exception définit la méthode spéciale __str__() appelée automatiquement lorsque l’on cherche à utiliser l’objet comme une chaîne de caractère (comme c’est le cas dans notre exemple), et renvoie l’attribut message. Voici donc le résultat des quelques lignes précédentes :

Nous avons une erreur : integer division or modulo by zero !

Pour savoir quel est le type de notre exception, consultez le nom de la classe : e.__class__.__name__. Ce qui nous permettrait d’effectuer un traitement comme ceci :

try:
    result = 10 / 0
except Exception, e:
    exception_type = e.__class__.__name__
    if exception_type == 'ZeroDivisionError':
        print('Erreur de division par zéro')
    else:
        print('Erreur %s : %s' % (exception_type, e))

Ainsi, vous traiterez différemment votre erreur selon son type. Mais la structure try … except permet de faciliter ça, en conditionnant la récupération des exceptions :

try:
    result = 10 / 0
except ZeroDivisionError, e:
    print('Erreur de division par zéro')
except Exception, e:
    print('Erreur %s : %s' % (e.__class__.__name__, e))

Ici, soit nous récupérons une erreur ZeroDivisionError, soit nous récupérons… n’importe quelle autre erreur, car la récupération conditionnelle traite également les sous-types. Toute exception héritant de Exception, notre except Exception, e saura récupérer n’importe quelle exception, et agit donc comme le default d’un switch. Dans l’exemple précédent, la ligne 4 s’exécutera seulement si on intercepte une exception ZeroDivisionError (ou un type héritant de ZeroDivisionError. Si on obtient n’importe quelle autre exception, on exécute la ligne 6. Si une exception d’un type non-traité est levée, python s’arrêtera avec un traceback et un message d’erreur (qui sera celui de l’exception)

Le bloc inclus dans le try est du code, il n’a rien de particulier. Il est donc évidemment possible d’imbriquer des blocs try … except. Comme ceci :

try:
    do_something()
    try:
        do_something_else()
    except Exception, e:
        raise UserWarning(e.message)

    try:
        do_all_the_things()
    except Exception, e:
        raise UserWarning(e.message)

except UserWarning, e:
    print('Le programme s’est arrêté avec le message suivant : %s' % e)

Si do_something_else() renvoie une exception, celle-ci est récupérée par le except ligne 5, qui renvoie elle-même une exception (avec le mot-clé raise), qui est alors récupérée par le try parent : on entre alors dans le dernier except.

Pour finir, voyons comment créer nos exceptions :

class MyException(Exception):
    pass

C’est aussi simple que ça : créez une classe vide qui hérite d’Exception, ou d’une autre exception (par exemple, pour hiérarchiser vos exceptions), vu que le nom de classe est ce qui permettra de récupérer conditionnellement vos exceptions.

try:
    raise MyException('Message')
except MyException, e:
    print(e)

Notre exception est levée, et est traitable comme une autre.

Une dernière chose : le mot-clé finally, qui est exécuté après une structure try … except, quel que soit le bloc traité, autrement dit peu importe qu’une exception ait été levée ou pas :

try:
    do_something()
except Exception, e:
    print(e)
finally:
    print('Nous avons exécuté le bloc.')

Un finally est utile notamment pour libérer des ressources. Par exemple, si un fichier est ouvert dans le try mais qu’une exception est levée avant sa fermeture, le finally peut le fermer dans tous les cas.

La nécessité des commentaires

Je suis tombé sur cet article, expliquant en quoi les générateurs de blogs statiques, comme Pelican (utilisé ici même) étaient un danger pour le logiciel libre. En gros, c’est centré sur l’absence de commentaires, hormis en passant par un service tiers intégré en javascript (comme disqus). Je ne suis absolument pas d’accord avec ses arguments, et, suite à un débat avec l’ami Taziden, je pense nécessaire d’expliquer mon point de vue, au sujet des commentaires et le reste.

Pour commencer, et par honnêté, je dois bien dire que le choix de me passer de commentaires a été en partie imposé par le choix technologique de Pelican. En partie seulement, parce que c’est moi qui ai fait ce choix, en connaissance de cause.

Revenons aux principes du blog

Aujourd’hui, tout internaute lisant des blogs est habitué à pouvoir laisser son avis sur un billet en bas de celui-ci. Cette habitude provient des moteurs de blog très utilisés, comme Wordpress, ayant intégré cette fonctionnalité. Mais ne cédons pas à la généralisation technologique. Je me souviens encore de l’époque où « un blog » signifiait un truc sur « .skyblog.com », la fameuse plateforme infestée de contenus d’une médiocrité infinie. Aujourd’hui, on pourrait remplacer Skyblog par Wordpress. On a indéniablement évolué, étant donné que Wordpress est un logiciel libre, et qu’une importante partie des blogs existants sous cette plateforme sont hébergés sur des serveurs persos. Mais dans l’idée, on tend quand même vers cette généralisation : un blog a nécessairement des commentaires, comme un moteur de recherche, des catégories, mots-clés, etc.

Mais pourquoi serait-ce le cas ? Un blog, ce n’est rien de plus qu’un journal personnel. Un site web rédactionnel, peu importe que le contenu soit de la veille technologique, des opinions politiques, ou des créations artistiques. En fait, c’est un espace d’expression pour l’auteur, qui en fait ce qu’il veut. Les moyens techniques pour arriver là sont : le protocole HTTP, et le langage HTML. Le web, en somme, ni plus ni moins. Un simple éditeur de texte, un coin sur un serveur web, et on peut écrire une page basique, et du contenu à l’intérieur. On a un blog, pas moins vrai que le wordpress du voisin ou celui-ci. Un blog n’a donc pas à avoir de fonctionnalités particulières.

La valeur des échanges

Évidemment, je ne cherche pas à nier l’importance des commentaires de blog, parfois bien plus intéressants que l’article lui-même, souvent enrichissants, pour l’auteur et les lecteurs, qui assistent alors à la continuité d’un éventuel débat lancé par le billet, ou des corrections, ajouts, etc…

Mais il s’agit de quelque chose qu’on ne remet pas en question. Les commentaires vont sous l’article, et (Wordpress y a beaucoup contribué) ils sont structurés de façon conventionnelle : un fil de messages chronologiquement ordonnés, avec parfois des avatars récupérés depuis un service tiers fermé (dont le traitement des données personnelles est douteux), parfois avec la possibilité de répondre à un commentaire particulier… Cette dernière fonctionnalité est intéressante, parce qu’elle permet à la discussion d’évoluer. D’un échange entre l’auteur et ses lecteurs, on peut obtenir un débat autonome, mais restant posté sur le blog.

L’avis de Taziden là-dessus est qu’il est indispensable pour un blog de proposer une fonctionnalité de commentaires. Pour moi, c’est plus une habitude tenace qu’un besoin. Pourquoi ne remettrait-on pas plutôt en question cette fonctionnalité, pour la faire évoluer ? Pourquoi, au lieu de commentaires, ne pas développer un système d’annotations collaboratives, fonctionnant telles des calques sur le billet, pour enrichir l’article directement, comme on le peut le faire avec Etherpad ? Il s’agit d’une simple idée d’évolution, et je regrette que si peu de projets aient le courage de remettre à plat les usages et habitudes pour proposer quelque chose de novateur (en cela, le projet Discourse m’intéresse énormément), plutôt que de se sentir obligés d’intégrer les fonctionnalités « habituelles ».

Malgré cela, un blog reste un espace personnel. C’est l’espace de l’auteur. Celui-ci ne souhaite pas nécessairement que ses articles soient commentés. Il arrive d’ailleurs parfois d’écrire pour soi-même, et pas pour être lu. Les commentaires sous un billet sont quelque chose que l’auteur peut proposer. C’est un service supplémentaire. Souvent très enrichissant, mais toujours optionnel. Il n’y a pas, je pense, lieu d’exiger de quelqu’un qu’il mette à disposition un tel service, sous prétexte qu’il blogue. Et c’est tout à fait normal, un blog n’est pas un service public ou quelque chose de démocratique.

INTERNEEEEET

J’ai une confidence à vous faire : mon blog permet les commentaires. D’ailleurs, il en a régulièrement. Le fait qu’ils ne soient pas nécessairement écrits sous chaque billet ne les rend pas inexistants ou invisibles.

Après la publication d’une grande partie de mes billets, il m’arrive de recevoir un mail (chiffré ♥) contenant des remarques sur celui-ci, et avec un patch attaché, contenant des corrections de forme. Je trouve ça génial, et ce n’est pas le genre de chose qui aurait été possible avec un Wordpress. Mais encore, lorsque je publie, je diffuse l’article sur plusieurs canaux, comme IRC ou Statusnet (qui est ensuite renvoyé sur Twitter, qui est fermé), ou encore des mailing-lists. Là, les retours se font, les discussions, débats ou trolls, selon le sujet, se lancent. Et je n’ai aucun contrôle dessus. C’est quelque chose de fondamental. Sur mon blog, je peux valider, dévalider, supprimer, bannir quelqu’un, parce que j’en ai le contrôle (après tout, c’est mon espace). Mais une des grandes forces d’Internet, c’est que la communication est possible, quel qu’en soit le moyen. Je suis fortement attaché à la liberté d’expression, vous le savez. Et je considère simplement qu’on est plus libre de discuter par le moyen ou protocole qu’on veut, plutôt que de façon centralisée sur un site.

Disqus ? CÉMAL !

À ce titre, la solution proposée, notamment par Pelican, pour permettre les commentaires, est généralement le service Disqus. Il s’agit d’une application chargée en javascript qui va permettre de commenter un article via un service externe. Ce service est totalement centralisé, fermé, et sous son propre contrôle. J’ignore si un blogueur a la possibilité de supprimer un commentaire posté sur disqus, mais je sais que la plateforme elle-même a ce droit. Et je ne sais pas qui est derrière. Qu’est-ce qui me garantit que Disqus ne considèrera pas unilatéralement que mon commentaire est un spam, ou contrevient à son éthique ?

L’auteur de l’article évoqué plus haut appelle au développement d’un équivalent libre à Disqus. À cela, je réponds « NOPE NOPE NOPE NOPE ». Un service tiers centralisé, qu’il soit libre ou non, c’est mal. Ne serait-ce que parce que, peu importe sa licence, je ne connais pas les personnes qui s’en occupent. Je n’ai aucune garantie sur le service : est-ce qu’il risque de tomber en panne et ainsi rendre inopérables les commentaires sur un grand nombre de blogs ? Est-ce que les données sont sécurisées ? Est-ce que le service ne risque pas de fermer du jour au lendemain ?

Ceci dit, je suis mauvaise langue. Rien n’empêche la création d’un service de commentaires décentralisé, que l’on pourrait installer sur son propre serveur pour servir ses propres commentaires. Ce serait une solution acceptable. Mais l’inclusion dans le blog se ferait en javascript, ce qui n’est pas très accessible. Je préfère me passer de javascript autant que possible, donc je n’utiliserais vraisemblablement pas une telle solution.

Au lieu de cela, je laisse les retours se faire naturellement. On est sur Internet, bon sang, les moyens de communication ne manquent pas. En réagissant par le biais de protocoles plus adaptés pour ça, on ne dépend plus du blog, de son auteur, d’un service tiers, et de tous les risques de censure en découlant.

Un autre avantage indirect est la possibilité de développer son avis par un autre billet de blog qui se présentera en réponse du premier. Cela favorise la discussion, qui est alors d’autant plus profonde qu’elle ne pourrait l’être en 140 caractères. Mon dernier billet est dans ce cas : je voulais répondre en commentaire à Numendil, mais il les avait désactivés, alors j’ai pris le temps de faire un billet complet.

On n’est pas dans une salle de conférence

On m’a rétorqué qu’il était important que les commentaires soient proposés par le blog lui-même, en faisant le parallèle avec une conférence, où le public est invités à la fin à poser des questions. Cela n’a, pour moi, pas plus de sens que la tristement célèbre comparaison entre Internet et une autoroute. Les contraintes physique n’ont juste rien à voir. Une conférence se déroule dans un lieu clos et défini, à un moment défini, et les personnes, pour se faire entendre, parlent l’une après l’autre, et s’adressent au conférencier. Un blog n’a aucune de ces contraintes. Par ailleurs, à certaines conférences auxquelles j’ai pu participer (comme Passage En Seine), les retours se faisaient aussi sur IRC, donc leur décentralisation est déjà possibe IRL, donc je ne vois pas le problème de laisser les retours se faire ailleurs que sous l’article.

Bad-buzz friendly

Il n’y a pas que de gentils barbus (visuel non contractuel) qui s’expriment sur les Internets. Il y a aussi des politiques vaguement pourris, des boîtes qui cherchent à nous plumer sans trop que ça se voie, bref des gens qui ont un intérêt économique (entre autres) à ce qu’on ne leur crache pas trop bruyamment dessus. Et donc qui n’hésiteront pas à modérer sévèrement les moyens de communications qu’ils daignent mettre en place, pour éviter tout débordement ou diffusion d’une mauvaise image. Dans ce cas, l’utilisation de canaux tiers est parfaitement indispensable, parce que certaines choses méritent d’être sues. Si on se limite aux moyens autorisés, ce n’est plus du minitel 2.0 qu’on utilise, mais de la télé 2.0, où les diffuseurs sélectionnent ce qui a le droit d’être dit. C’est un modèle du passé, et il faut absolument éviter de restreindre la communication sur Internet à ces espaces de discussion qu’on veut bien nous laisser.

Donc non, un blog statique ne nuit pas au logiciel libre. À l’inverse, il encourage à l’utilisation épanouie d’Internet, c’est à dire à la décentralisation des échanges, à l’utilisation de protocoles prévus pour ça.

À ceux qui râlent

Je n’ai pas l’habitude d’écrire ce genre d’articles. Il s’agit d’une réponse à un billet de quelqu’un que je considère encore comme un ami de valeur, au sujet du féminisme, ou plus exactement des débats qu’il occasionne ces derniers temps. J’ai initialement voulu répondre directement en commentaire, mais parce qu’il avait fait le choix de les fermer, je me retrouve à écrire ici. J’en profite pour mettre à plat certaines de mes opinions à ce sujet, en espérant ne pas trop faire doublon avec mes articles précédents, notamment celui-ci.

Le féminisme extrémiste (celui avec des grandes dents)

Bon tout d’abord, je ne sais pas combien de fois il va falloir le rappeler : il n’y a pas « le » féminisme. Pour rappel, il s’agit de revendication de l’égalité des sexes et des genres. L’interprétation là-dessus est libre, ce qui donne un tas de militantismes différents. Et crois-moi Jérôme (prenez l’habitude, je m’adresse à une personne particulière dans ce billet), le féminisme extrémiste, ce n’est pas ce que tu vois. Le féministe extrémiste flirte sans gêne avec la misandrie, et ça se voit très vite. Et de tou·te·s les féministes que je connais, personne ne soutient ces idées. Si vraiment tu veux lire ce que je considère comme du féministe extrémiste (ce qui est revendication de l’auteure), je te conseille de lire ceci. Je préfère te prévenir, c’est vraiment violent. Ce que tu appelles « féminisme extrémiste », ce sont des gens très visibles sur twitter qui passent une grande partie de leur temps à se plaindre. Se plaindre de situations vécues, de réponses qu’on leur fait, d’articles lus, etc. Crois-moi, personne ne fait ça de gaieté de cœur. Seulement, quand on est sensibilisé au problème du sexisme, on a la fâcheuse tendance à le voir partout. Et tu sais particulièrement combien il est difficile de laisser couler des injustices qui sont sous notre nez.

La pilule rouge

Le fait est que ton article dénonce. Et qu’est-ce qu’il dénonce ? Le comportement de certain·e·s féministes. Ce qui est intéressant (et ce qui découle également de la discussion eue plus tôt avec deux autres hacktivistes), c’est justement le sens de cette dénonciation. Pierre, Élodie ou toi, vous êtes évidemment contre le sexisme. Dans les deux sens, je suis d’accord. Et quand vous écrivez sur le sujet, c’est immanquablement pour… taper sur des féministes. C’est révélateur, tu ne trouves pas ? N’aurait-il pas été plus utile, efficace ou que sais-je de dénoncer le sexisme concrètement ? Par exemple, dans ton billet, tu évoques des agressions que tu as subies, et que tu prends avec légèreté. Chacun réagit à sa façon à ce genre d’actes, et je n’ai rien à redire à la façon dont tu le prends. Contrairement à ce que l’on peut croire, je ne considère en aucun cas qu’être victime d’agression doit marquer à vie et nous forcer à vivre dans la honte et la culpabilité. Par contre, notre culture joue un rôle important là-dedans, dans le fait que pour toi ça n’ait pas été très grave. Car beaucoup de femmes prennent ce genre d’acte bien moins légèrement que toi. Peut-être que leur fréquence y est pour quelque chose ? Je soupçonne que oui, mais je n’en ai pas la preuve. Par contre, ce qui est avéré, c’est la culture du viol. Le fait que les jeunes filles, dès la puberté, apprennent à avoir peur, à redouter les agressions sexuelles. Agressions qui arrivent d’ailleurs en très grande majorité dans le cercle privé ou familial, mais c’est un autre sujet. Tu ne peux ignorer qu’on conseille aux femmes de ne pas sortir seules le soir, de faire attention à elles, etc. Face à ça, comprends-tu la gêne, la peur, quand un quelconque gentleman aborde une femme, qui n’a rien demandé, dans un espace qu’elle considère culturellement comme dangereux ? Sachant également que ce qu’on leur apprend à craindre est considéré comme banal, ne serait-ce qu’au moment de porter plainte si l’on en a le courage (difficile de porter plainte contre un membre de sa famille, surtout lorsque c’est sur nous que sera jetée la honte dans la majorité des cas…). Vois-tu où je veux en venir, et pourquoi ton ressenti face à des agressions (qui sont condamnables avec la même fermeté que si tu avais été une femme, fût-il utile de le préciser) est forcément différent de celui d’une femme ?

On me reproche, face à ça, de considérer les femmes comme des victimes en puissance. Ce n’est pas vrai. Les femmes sont ciblées par des actes sexistes à différentes échelles. Elles les subissent. Mais pourquoi une agression devrait-elle entraîner la honte ou peur de sa cible ? Encore une fois, toute cette discussion part de personnes qui ouvrent trop leur gueule. Qui n’ont justement pas du tout une posture de victime, mais au contraire, décident de ne plus se laisser faire, qu’il s’agisse de se défendre d’une agression physique ou de protester contre un geste commun de la vie quotidienne, mais qui participe à la société patriarcale. Car, quand on choisit la pilule rouge, qu’on accepte de remettre en cause notre culture, notre société, et, au fond, nous, on voit l’envers du décor, la matrice. On voit à quel point le sexisme est partout, et on perd facilement les pédales. C’est tout simplement ce qui arrive lorsque quelqu’un comme moi se met à réagir au quart de tour sur twitter. On pense constamment « Mais comment ne peuvent-ils pas voir ces injustices ? ». Je me trompe peut-être à ce sujet, mais je vous vois (vous, qui écrivez pour dénoncer l’attitude ou les propos de féministes énervé·e·s) comme des personnes, non pas ayant choisi la pilule bleue, mais surtout qui n’ont pas été confrontées à ce choix. Imaginez le film Matrix si, au lieu de latter des vilains en costard et lunettes noires, les héros avaient pour but d’expliquer à la population qu’ils sont dans la matrice… J’aime à penser que ça donnerait des situations similaires à ce que nous vivons. Ceci dit, dans mon analogie, le camp des héros est clairement défini, et il s’agit comme par hasard de celui que je m’attribue. J’aimerais beaucoup connaître votre avis là-dessus.

Quoi qu’il en soit, une fois la pilule rouge avalée, on commence à remettre en question notre environnement. Lors d’un débat, on s’aperçoit que la seule femme présente n’arrive pas à en placer une ou à se faire écouter. On remarque également les remarques, davantage tournées vers son apparence que vers ses idées. On tend un peu plus l’oreille quand on entend une femme se plaindre du comportement des gens dans la rue (et pas seulement des hommes). Parfois, et c’est plus difficile, on se prend soi-même à lâcher une parole qui autrefois n’aurait pas posé problème, mais qui aujourd’hui sonne faux, parce que vous remarquez qu’elle est discriminante et gratuite. Ça peut être quelque chose d’aussi insignifiant en apparence que le fait d’user de « Mademoiselle » pour s’adresser à une jeune femme, ou bien un glacial « t’as vu comment tu t’es habillée, aussi ? » à une femme qui raconte comment elle a failli être violée quelques instants plus tôt. Je comprends que cela provoque un agacement : on déterre toutes ces petites choses qui composent notre vie de tous les jours, toutes ces choses acquises, avec lesquelles on vit. Et remettre en question sa culture est une chose quand on a choisi d’ouvrir les yeux, mais se le prendre en pleine gueule alors qu’on ne s’en soucie pas peut effectivement mettre très mal à l’aise, et occasionner le l’agressivité en signe de défense.

C’est ce qui arrive lorsque que l’on dénonce le sexisme dans la société, mais c’est exactement pareil dans les communautés, comme les geeks. À la différence près que, quand on s’identifie comme geek, on s’attache à cette culture. Et inévitablement, on tombe dans le communautarisme. Que ce soit pour le milieu geek ou les autres. La réponse hautaine qu’on va tenir à un nouveau qui demande des conseils candides (et parfois un peu idiots) dans notre domaine d’expertise, c’est du communautarisme. On a notre communauté, et on veut naturellement s’assurer qu’on n’y entre pas n’importe comment, car c’est un espace familier. Quand on tape parfois brutalement sur Tris quand elle parle de sécu, quand bien même c’est argumenté, c’est du communautarisme, et je reconnais en faire moi-même. Voyez, je reconnais reproduire le problème, et j’en suis conscient. Et pourtant, je continue à lui dire ce que je pense, ce qui est rarement glorieux. Ridicule, vous ne trouvez pas ? D’ailleurs, je ne passe pas à côté du sexisme très installé dans certaines œuvres, comme la série Game of Thrones. Et pourtant, je l’aprécie. Parfois, le fait d’avoir cette analyse inconsciente des constructions genrées dans la fiction gâche le plaisir de la découverte. C’est comme ça, c’est ça le revers de la pilule rouge. On voit le monde encore plus sombre qu’avant. Mais on sait qu’on a alors les clés en main pour changer ça à notre échelle, en commençant par moins reproduire ces schémas. « Moins », et pas « plus », car la tâche est monumentale, et la seule façon de refuser du jour au lendemain toute forme de sexisme serait de couper tout lien avec la société, et de subir un lavage de cerveau. Ce qui ne serait pas très efficace. Alors on accepte de vivre dans ce monde sexiste, on continue d’aprécier des œuvres, même si elles se montrent sexistes. Il faut savoir faire des compromis, et connaître les limites de son militantisme.

Revenons au sujet

Dans ton article, Jérôme, tu persistes à expliquer en quoi tu réfutes le terme de féminisme pour parler d’égalité. Je suis égalitariste, tu le sais. Je ne souhaite pas inverser les privilèges, je souhaite que femmes et hommes soient au même niveau social, aient les même chances par défaut dans la vie.

Mais.

Être pour l’égalité sans aller plus loin, c’est une bien belle déclaration, qui n’a d’égale à son ardeur que son inutilité. C’est une déclaration de principe, rien de plus. Parce qu’il est impossible d’évoquer l’égalité hommes-femmes sans évoquer le patriarcat. Or, je te n’ai jamais vu ne serait-ce qu’employer ce mot. Sais-tu seulement ce qu’il signifie ? Il signifie que la société dans laquelle on vit a été pensée par et pour des hommes, et que les femmes y sont réduites à l’état de ressource. Évidemment, la société a évolué, et la place des femmes avec elle. Reste qu’historiquement, la domination était très claire : les hommes gouvernaient, et les femmes obéissaient. Aujourd’hui, est-ce différent ? « On n’est plus dans les années 50 », mais on n’est pas encore dans les années 10 000. Autrement dit, il y a beaucoup de chemin. Des exemples ? Le plafond de verre, qui symbolise l’extrême difficulté qu’ont les femmes à atteindre les postes à responsabilités. Ou l’inégalité salariale, ou encore la culture du viol, conçue pour culpabiliser les victimes et souvent d’excuser les auteurs. Ou encore la banalité que représente l’abordage d’une femme dans la rue, qui n’a rien demandé. Le patriarcat enseigne qu’on le peut, et qu’une femme doit se faire belle pour plaire aux hommes.

Le patriarcat, c’est une composante majeure de notre société. Il est normal que ça couine un peu quand on tape dessus. En tout cas, si tu souhaites lutter pour l’égalité, il y a une chose importante à faire. Et difficile. C’est le choix de la pilule, dont je parlais plus haut. Admettre l’existence du partiarcat, pour commencer (c’est pas si évident). Et surtout, admettre qu’en tant qu’homme, tu es naturellement privilégié. Ce n’est pas une honte, une critique ou quoi que ce soit. D’ailleurs tu n’y peux rien, tu n’as pas choisi ton genre. Mais c’est un fait, tu fais partie de la « caste » mise en avant par le patriarcat. Il y a fort à parier que l’immense majorité des gens qui liront ce billet sont privilégiés, d’une façon ou d’une autre. Toi, Pierre et moi, nous sommes des hommes blancs hétérosexuels cis (c’est à dire qui vivons notre genre assigné à la naissance). Cela fait 4 raisons d’être privilégiés. Nous sommes réellement quasiment au sommet de la pyramide sociale des discriminations. Ce sont les femmes qui sont victimes du patriarcat. Ce sont tous ceux dont la couleur de peau n’est pas celle des anciens conquérants européens qui sont victimes du racisme. Ce sont les homosexuel·le·s, bis, transgenres et queers qui sont victimes d’homophobie ou de transphobie. Pas nous. Je le répète, ce n’est pas un jugement, mais un constat. En admettant ses privilèges, on accepte de faire partie du problème. À ce moment, ça ne devient plus « des râleuses qui nous emmerdent avec leurs problèmes dont on n’a rien à faire ». On sait qu’on en est aussi, qu’on participe à ça. Après, on est libre de changer nos habitudes. D’accepter, non pas de renoncer à ces privilèges, mais de travailler à les partager. Ce n’est bien sûr pas un acte individuel, et je ne peux pas dire « tiens, prends ma place d’homme aux yeux de la société ». Pas par faute d’envie, mais parce que changer mon regard n’est qu’une pierre à l’édifice. Pour que ça ait un impact, il faut naturellement qu’une masse critique ait conscience de ce problème, et fasse un effort pour y remédier. Cette masse critique, c’est vous et moi, pas « les vilains misogynes ». Admettre ça, c’est lutter pour l’égalité des genres. C’est être féministe.

Et toi, Jérôme, qui es pour l’égalité, t’es-tu remis en question ? Ou te contentes-tu de taper sur celles et ceux qui s’efforcent de sensibiliser les autres ? Au contraire, ai-je envie de dire. Car quand tu dénonces le fait que le dossier de Mar_Lard pointe du doigt « les geeks », il est clair que tu te sens vexé, car on sous-entend que ta précieuse communauté pourrait avoir un problème, pire, que tu pourrais en faire partie. Non content d’être du communautarisme bien idiot, il s’agit d’un rejet pur et dur du problème. Tu préfères penser que le sexisme, bien sûr que ça existe, mais ce sont « les autres ». Pas de ça autour de toi, sinon tu l’aurais vu, n’est-ce pas ? Et si c’était le simple fait de ne pas le remarquer qui te mettait mal à l’aise ? Tu sais, depuis le (faible) temps que je milite pour l’égalité, j’ai fini par repérer les patterns courants de ceux qui tapent sur le féminisme, mais qui sont d’accord sur le principe, hein, mais… Mais faut pas crier aussi fort, mais faut pas généraliser sur les geeks, y’en a des biens… Et là, je ne te fais que les réponses construites. Parce que dans leur immense majorité, les réactions sont purement haineuses. As-tu jeté un œil sur les commentaires du moindre article rédigé par Mar_Lard ? Toi qui parles d’égalité, et de ses billets, n’as-tu pas trouvé pertinent, même si tu souhaitais critiquer intelligemment, de signaler cet état de fait ? D’ailleurs, qu’en penses-tu, toi qui, sans le vouloir, participes à la levée de boucliers qui suit inévitablement ce pavé dans la mare ?

Non pas que le dossier dont il est question ne soit pas critiquable. Au contraire. Il s’agit du travail personnel de quelqu’un de passionnée par le sujet, et dont ce n’est pas le métier. Ça se ressent, évidemment. Mais on va lui reprocher de ne pas parler de quelque chose, d’avoir un ton incorrect. Tout est prétexte à critiquer son texte. Ça a quelque chose de malsain, et je dois dire que je t’en veux de ne pas avoir vu plus loin que ça.

Finalement, tu as raison, tas de pixels. C’est bien une question de binaire. La pilule rouge ou bleue. La reconnaissance du problème ou son déni.

Bonjour Axolotl !

Il y a quelques semaines, on m’a parlé de la possibilité d’adopter des axolotls. Je connaissais vaguement ces bestioles, mais la proposition m’a donné envie d’en apprendre plus sur elles. Après lecture de la page Wikipedia, je suis tombé sous le charme de ces bestioles. Voyez par vous-mêmes : une bestiole qui sourit tout le temps, qui passe sa vie sous forme de larve, qui peut se regénérer jusqu’à des parties de son cerveau (Arme X ?), qui a des branchies rigolotes et poilues, et surtout, qui mange comme un idiot tout ce qui est plus petit que lui. Je trouve ça génial.

Un axolotl

J’ai rapidement eu l’idée de monter un petit site, dans la lignée des Bonjour le chat et autres variations plus portées sur les humain·e·s, c’est à dire un blog simpliste postant une photo chaque matin. La quasi-totalité de ces sites est hébergée et gérée par Tumblr, mais vous connaissez mon amour pour les plateformes centralisées et pseudo-gratuites. Je suis donc parti sur Pelican, comme pour mon blog, avec dans l’idée la possibilité d’automatiser la création de posts.

Le travail a donc consisté à :

  • installer un Pelican
  • créer un template Jinja pour Pelican reprenant le thème simpliste utilisé sur les bonjour* ;
  • créer un script de publication automatique ;
  • récupérer des photos d’axolotls ;
  • ???
  • PROFIT.

Installation

Le bon sens et la conception de Pelican veulent que le contenu soit généré depuis le poste local, pour être envoyé vers le serveur sans qu’il ne soit nécessaire d’avoir un interpréteur Python sur celui-ci. Dans mon cas, souhaitant une génération automatique, il m’a semblé plus propice de tout déléguer au serveur. Ainsi, j’ai installé Pelican sur mon serveur en suivant le guide, et configuré le serveur web pour servir le dossier output.

Je me suis aperçu après coup qu’une fonctionnalité intéressante me manquait dans Pelican : la possibilité, pour chaque article, d’avoir le lien vers son prédécesseur et/ou successeur, pour obtenir une navigation simple. Ce fonctionnement peut être obtenu via un plugin : Neighbors. Celui-ci, une fois téléchargé, se place à la racine du projet Pelican, et on l’inclut via le fichier de configuration, que voici :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env python
# -*- coding: utf-8 -*- #

AUTHOR = u'Axoloto'
SITENAME = u'Bonjour Axolotl'
SITESUBTITLE = u'Le site qui vous fera dire « Bonjour l’<a '\
              +u'href="https://fr.wikipedia.org/wiki/Axolotl">axolotl</a> ! »'\
              +u'<br /><br />Tous les matins, 10h, une nouvelle <strong>photo'\
              +u'</strong> d’<strong>axolotl</strong> !'
SITEURL = 'http://bonjouraxolotl.fr'

TIMEZONE = 'Europe/Paris'

DEFAULT_LANG = u'fr'

# Blogroll
LINKS =  (('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'),
          ('Python.org', 'http://python.org'),
          ('Jinja2', 'http://jinja.pocoo.org'),
          ('You can modify those links in your config file', '#'),)

# Social widget
SOCIAL = (('You can add links in your config file', '#'),
                  ('Another social link', '#'),)

DEFAULT_PAGINATION = 1

THEME = 'template'
FEED_DOMAIN = 'http://bonjouraxolotl.fr'
FEED_RSS = '/feeds'
FEED_ATOM =  None

PLUGINS = ["neighbors"]

Dans les quelques trucs intéressants, notez que j’ai restreint les pages à 1 seul article, et que j’ai inclus le plugin à la dernière ligne. Le reste est très simple. J’ai aussi choisi d’utiliser du RSS au lieu d’Atom.

Création du template

Je me suis basé sur le markup généré par bonjourlechat.fr pour créer mon template. Il n’y a basiquement besoin que de 3 pages : la page d’accueil, la page d’un article, et la page d’archives. D’ailleurs, je ne me suis même pas préoccupé de cette dernière…

Pour commencer, j’ai copié le template notmyidea, situé dans $PELICAN_INSTALL_PATH/themes/notmyidea, dans la racine du projet, renommé en « template ». À l’intérieur de celui-ci, on trouve deux dossiers : static et templates. Le premier sera copié tel quel dans le dossier output, tandis que le second contient le code Jinja permettant de générer les pages. Celles qui nous intéressent sont base.html, index.html, article.html et article_infos.html. La différence entre les deux derniers est que article.html est la page complète d’un article tandis que article_infos* n’est que le bloc HTML d’un article seul. Je l’ai mis dans un fichier à part pour pouvoir l’inclure facilement depuis les différentes pages sans avoir besoin de le réécrire. voici le contenu des fichiers :

base.html

<!DOCTYPE html>
<html lang="en">
<head>
        <title>{% block title %}{{ SITENAME }}{%endblock%}</title>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="{{ SITEURL }}/theme/css/{{ CSS_FILE }}" type="text/css" />
        {% if FEED_ALL_ATOM %}
        <link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" />
        {% endif %}
        {% if FEED_ALL_RSS %}
        <link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
        {% endif %}
</head>

<body id="index" class="home">
    <div id="wrapper">
        <div id="topNav">
            <ul>
                <li><a href="/archives.html">Archives</a></li>
                <li><a href="{{ FEED_DOMAIN }}/{{ FEED_ADD_RSS }}">RSS</a></li>
            </ul>
        </div>
        <div id="contentHolder">
            <div id="mastHead">
                <h1><a href="{{ SITEURL }}/">{{ SITENAME }}</a></h1>
                <p>
                    <span style="font-family: Arial">{{ SITESUBTITLE }}</span>
                </p>
            </div>
            <div id="content">
                <div id="postHolder">
                    {% block content %}
                    {% endblock %}
                </div>
            </div>
        </div>
        <div id="footer">
            <p style="text-align:center">
                Fièrement propulsé par <a href="http://getpelican.com/">Pelican</a>,
                qui se repose sur <a href="http://python.org">Python</a>.
            </p>

            <p style="text-align: center">Thème par
            <a href="http://daelan.com/">Daelan</a>, merci !</p>
        </div><!-- /#contentinfo -->
    </div>
</body>
</html>

article.html

{% extends "base.html" %}
{% block title %}{{ article.title|striptags }}{{ SITENAME }}{%
    endblock %}
{% block content %}
    {% include 'article_infos.html' %}
{% endblock %}

article_infos.html

<div class="post">
    <div class="labels">
        <div class="date">
            <a href="{{ SITEURL }}/{{ article.url }}">
                <span class="month">{{ article.date.strftime('%d %m') }}</span> 
                <span class="year">{{ article.date.year }}</span>
            </a>
        </div>
        <div id="navigation" style="position: absolute; top:0; right: 0;">
            {% if article.prev_article %}
                <a href="{{ article.prev_article.url }}"><b>«</b></a>
            {% endif %}
            {% if article.next_article %}
                <a href="{{ article.next_article.url }}"><b>»</b></a>
            {% endif %}
        </div>
    </div>
    <div class="photo">
        <div class="permalink">
            <a href="{{ SITEURL }}/{{ article.url }}">+</a>
            {{ article.content }}

            <div class="caption"><p>{{ article.title }}</p></div>
        </div>
    </div>
</div>

Un dernier détail : le CSS. J’ai copié bêtement le CSS du site cible, que j’ai mis dans template/css/main.css :

/* -- reset this mutha!  -- */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
    margin: 0;
    padding: 0;
    border: 0;
    outline: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
}

body {
    line-height: 1;
}

ol, ul {
    list-style: none;
}

blockquote, q {
    quotes: none;
}

blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}

/* remember to define focus styles! */
:focus {
    outline: 0;
}

/* remember to highlight inserts somehow! */
ins {
    text-decoration: none;
}

del {
    text-decoration: line-through;
}

/* -- end reset  -- */

body{
    margin:0;
    padding:6px;
    text-align:center;
    font-family: Helvetica, Arial, Verdana, Sans-Serif;
    background: #fff;
}

#wrapper{
    width:895px;
    height:auto;
    margin:0 auto;
    padding:0;
    text-align:left;
}

#topNav{
    position:relative;
    float:left;
    width:895px;
    height:90px;
    margin:0;
    padding:0;
}

#topNav ul{
    position:relative;
    float:right;
    margin:0 0 0 0;
}

#topNav ul li{
    position:relative;
    float:left;
    margin:0 0 0 20px;
}

#topNav ul li a:link,#topNav ul li a:visited{
    font-size:12px;
    color:#000000;
    text-decoration:none;
    background:#FFFFFF;
    border:0;
    padding:3px 6px 3px 6px;
    line-height:18px;
}

#topNav ul li a:hover{
    color:#FFFFFF;
    background:#0000FF;
}

#contentHolder{
    position:relative;
    float:left;
    width:895px;
    height:auto;
    min-height: 500px;
}

#mastHead{
    position:relative;
    float:left;
    width:395px;
    height:auto;
    margin:0 0 0 0;
    display:inline;
}

#mastHead h1{
    margin:9px 18px 18px 0;
}

#mastHead h1 a:link,#mastHead h1 a:visited{
    letter-spacing:-2px;
    font-size:36px;
    background:#0000FF;
    padding:18px;
    line-height:54px;
    border:0;
    text-decoration:none;
    color:#FFFFFF;
}

#mastHead h1 a:hover{
    background:#0000FF;
    color:#000000;
}

#mastHead p{
    font-family:Georgia, "Times New Roman", Serif;
    font-family:#000000;
    font-size:11px;
    padding:9px 36px 9px 18px;
    line-height:18px;
    margin:0;
}

#mastHead form{
    padding:9px 18px 9px 18px;
}

#mastHead input{
    padding:0;
    margin:0;
}

#mastHead a:link, #mastHead a:visited{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

#mastHead a:hover{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

div.photo img{
    border:0;
}

a.hyperLink:link,a.hyperLink:visited{
    color:#0000FF;
    text-decoration:none;
    font-size:18px;
    line-height:18px;
    font-weight:bold;
    border:0;
}

a.hyperLink:hover{
    color:#FFFFFF;
}

.description{
    position:relative;
    float:left;
    width:100%;
    height:auto;
    margin:18px 0 18px 0;
}

.post{
    position:relative;
    float:left;
    border-bottom:1px dotted #ccc;
    padding:0 0 18px 0;
    width:100%;
    height:auto;
    font-size:11px;
    line-height:18px;
    margin:0 0 18px 0;
}

.labels{
    margin:0;
    padding:0;
}

#content{
    position:relative;
    float:left;
    width:500px;
    height:auto;
    margin:0 0 0 0;
    display:inline;
}

#content p{
    font-size:11px;
    line-height:18px;
    margin:0 0 18px 0;
}

#content h2{
    position:relative;
    float:left;
    width:500px;
    padding:0 0 0 0;
    height:auto;
    line-height:18px;
    font-size:18px;
    margin:0 0 18px 0;
}

#content h2 a:link,#content h2 a:visited{
    color:#0000FF;
    text-decoration:none;
    border:0;
}

#content h2 a:hover{
    color:#FFFFFF;
}

.date{
    border-bottom:1px dotted #666;
    float:left;
    font-size:18px;
    font-weight:bold;
    letter-spacing:-1px;
    margin:0 0 18px 0;
    padding:0;
    position:relative;
    text-transform:uppercase;
    line-height:18px;
    width:500px;
}

.year{
    color:#0000ff;
    letter-spacing:0;
}

.month{
    color:#0000FF;
    letter-spacing:0;
}

#content a:link, #content a:visited{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

#content a:hover{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

#footer{
    position:relative;
    float:left;
    width:895px;
    height:auto;
    border-top:1px dotted #efefef;
    padding:18px 0 0 0;
    margin:18px 0 18px 0;
}

#footer p{
    font-size:11px;
    line-height:18px;
    margin:0 20px 0 20px;
}

#footer a:link, #footer a:visited{
    text-decoration:none;
    color:#000;
    border-bottom: 1px dotted #FFFFFF;
}

#footer a:hover{
    text-decoration:none;
    color:#0000FF;
    border-bottom: 1px dotted #FFFFFF;
}

.caption{
    position:relative;
    float:left;
    margin:18px 0 18px 0;
    width:100%;
    height:auto;
}

.regular, .quote, .video, .photo, .audio, .conversation, .link{
    position:relative;
    float:left;
    width:100%;
    height:auto;
}

big{
    font-size:24px;
    line-height:18px;
    padding:0 5px 0 0;
}

.source{
    color:#666;
}

.permalink{
    position:absolute;
    top:0;
    left:-18px;
    height:18px;
    margin:0;
    padding:0;
    line-height:18px;
}

#navigation {
    font-size: 22px;
    font-weight: bold;
}

#pages{
    font-weight:normal;
    color:#999;
    padding:0;
    font-size:11px;
    margin:10px 0 0 0;
}

Voilà tout pour la partie Pelican. Vous avez un beau site, vide mais beau. Maintenant, remplissons-le dynamiquement.

Le contenu

Les sites bonjour* ont généralement une légende sous chaque photo. Disons qu’on s’en fout. J’ai opté pour un fonctionnement minimaliste : on pose des images dans un dossier, et le script, appelé une fois par jour, prend la plus ancienne pour en faire un article puis la supprime.

# -*- coding: utf-8 -*-

import os
import locale
from datetime import date
import Image

locale.setlocale(locale.LC_TIME, 'fr_FR.utf-8')

content_dir = 'content'
content_image_dir = 'images'
source_dir = 'source'
max_size = (500, 500)

def create_article(filename):
    today = date.today()
    output_filename = '%s.jpg' % today.strftime('%Y-%m-%d')
    im = Image.open(os.path.join(source_dir, filename))
    im.thumbnail(max_size)
    im.save(os.path.join(content_dir, content_image_dir, output_filename))

    article_vars = {'today_str': date.today().strftime('%a %d %b %Y'),
                    'date': today.strftime('%Y-%m-%d'),
                    'output_filename': output_filename}

    article = """Title: %(today_str)s
Date: %(date)s

![%(today_str)s](./images/%(output_filename)s)""" % article_vars
    with open(os.path.join(content_dir, output_filename.replace('jpg', 'md')), 'w') as article_file:
        article_file.write(article)

file_list = os.listdir(source_dir)
oldest_file = None

for cur_file in file_list:
    cur_mtime = os.path.getmtime(os.path.join(source_dir, cur_file))
    if not oldest_file or cur_mtime < oldest_mtime:
        oldest_file = cur_file
        oldest_mtime = cur_mtime

create_article(oldest_file)
os.remove(os.path.join(source_dir, oldest_file))

Enregistrez ça dans la racine du site, sous le nom content_generator.py par exemple. C’est testé sous python 2.6 et 2.7. À chaque appel, il listera les fichiers dans source (vous devriez créer ce dossier), les classera par date de modification, puis dépilera la première pour en faire un article : il la redimensionnera aux dimensions voulues, puis écrira un article basique en Markdown, contenant seulement l’image et la date.

À ce niveau, le billet sera écrit mais pas encore publié. C’est un script bash qui s’en chargera, et qui sera appelé par cron. Enregistrez ce qui suit dans cron.sh à la racine du site :

1
2
3
4
5
6
#!/bin/bash

PATH="/usr/local/bin:/usr/bin:/bin"
cd /path/to/bonjouraxolotl
python content_generator.py
make html

Adaptez bien évidemment les chemins. Il ne reste plus qu’à éditer la crontab :

# crontab -l
0 10 * * * /path/to/bonjouraxolotl/cron.sh

Tous les matins à 10h, un nouvel axolotl !

EDIT : corrections et amélioration du script python par Rogdham, merci à lui.