First version

This commit is contained in:
Pascal Le Merrer 2025-12-13 16:16:45 +01:00
commit f6501517d5
54 changed files with 3455 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

75
Makefile Normal file
View file

@ -0,0 +1,75 @@
PY?=
PELICAN?=pelican
PELICANOPTS=
BASEDIR=$(CURDIR)
INPUTDIR=$(BASEDIR)/content
OUTPUTDIR=$(BASEDIR)/output
CONFFILE=$(BASEDIR)/pelicanconf.py
PUBLISHCONF=$(BASEDIR)/publishconf.py
DEBUG ?= 0
ifeq ($(DEBUG), 1)
PELICANOPTS += -D
endif
RELATIVE ?= 0
ifeq ($(RELATIVE), 1)
PELICANOPTS += --relative-urls
endif
SERVER ?= "0.0.0.0"
PORT ?= 0
ifneq ($(PORT), 0)
PELICANOPTS += -p $(PORT)
endif
help:
@echo 'Makefile for a pelican Web site '
@echo ' '
@echo 'Usage: '
@echo ' make html (re)generate the web site '
@echo ' make clean remove the generated files '
@echo ' make regenerate regenerate files upon modification '
@echo ' make publish generate using production settings '
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
@echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 '
@echo ' make devserver [PORT=8000] serve and regenerate together '
@echo ' make devserver-global regenerate and serve on 0.0.0.0 '
@echo ' '
@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html '
@echo 'Set the RELATIVE variable to 1 to enable relative urls '
@echo ' '
html:
"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
clean:
[ ! -d "$(OUTPUTDIR)" ] || rm -rf "$(OUTPUTDIR)"
regenerate:
"$(PELICAN)" -r "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
serve:
"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
serve-global:
"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b $(SERVER)
devserver:
"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
devserver-global:
"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b 0.0.0.0
publish:
"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS)
rsync -e ssh -av --delete-after /Users/pascal/Documents/craft-letter/output/ craftletter@ssh-craftletter.alwaysdata.net:/home/craftletter/www
ssh:
ssh craftletter@ssh-craftletter.alwaysdata.net
.PHONY: html help clean regenerate serve serve-global devserver devserver-global publish

Binary file not shown.

Binary file not shown.

BIN
content/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="629.69px" height="136.735px" viewBox="0 0 629.69 136.735" enable-background="new 0 0 629.69 136.735"
xml:space="preserve">
<g>
<g>
<path fill="#00819E" d="M37.601,5.57c5.764,0,10.667,0.764,14.709,2.292c4.042,1.529,7.746,3.685,11.112,6.47L54.079,25.59
c-2.205-1.818-4.642-3.259-7.311-4.323c-2.669-1.064-5.571-1.596-8.704-1.596c-3.365,0-6.441,0.958-9.226,2.873
c-2.785,1.914-5.01,4.932-6.673,9.051c-1.664,4.12-2.495,9.468-2.495,16.044c0,9.671,1.75,16.702,5.25,21.092
s7.999,6.585,13.491,6.585c4.023,0,7.37-0.754,10.038-2.264c2.67-1.508,5.126-3.152,7.37-4.932l8.646,11.024
c-3.017,2.979-6.731,5.533-11.141,7.66c-4.41,2.127-9.729,3.192-15.957,3.192c-7.273,0-13.722-1.617-19.351-4.847
c-5.628-3.229-10.038-7.986-13.23-14.273C1.596,64.592,0,56.846,0,47.639c0-8.974,1.653-16.595,4.96-22.862
c3.308-6.267,7.805-11.034,13.492-14.303C24.138,7.205,30.521,5.57,37.601,5.57z"/>
<path fill="#00819E" d="M74.387,88.024V75.723h8.123V38.587h-8.123V26.401h22.107l3.365,13.868
c2.206-5.299,5.02-9.244,8.443-11.836c3.423-2.591,7.61-3.888,12.563-3.888c2.088,0,3.945,0.165,5.571,0.493
c1.625,0.329,3.152,0.783,4.583,1.364l-3.423,25.764h-11.489V40.792c-3.481,0.619-6.538,2.504-9.167,5.657
c-2.631,3.154-4.662,7.031-6.093,11.634v17.64h12.186v12.302H74.387z"/>
<path fill="#00819E" d="M195.31,70.269c0,2.32,0.319,4.004,0.958,5.048c0.638,1.044,1.672,1.839,3.104,2.379l-3.771,12.069
c-3.637-0.348-6.702-1.151-9.198-2.408c-2.495-1.257-4.458-3.163-5.889-5.715c-2.36,2.823-5.377,4.923-9.052,6.297
c-3.675,1.372-7.427,2.06-11.257,2.06c-6.344,0-11.401-1.811-15.173-5.427c-3.771-3.616-5.657-8.27-5.657-13.955
c0-6.691,2.62-11.856,7.862-15.493c5.241-3.635,12.621-5.454,22.137-5.454h8.297v-2.321c0-6.304-4.061-9.458-12.185-9.458
c-1.973,0-4.507,0.281-7.601,0.841c-3.096,0.561-6.19,1.364-9.284,2.408l-4.236-12.186c3.984-1.509,8.133-2.649,12.447-3.423
c4.313-0.773,8.171-1.161,11.576-1.161c9.167,0,15.947,1.866,20.338,5.6c4.39,3.733,6.585,9.12,6.585,16.159V70.269z
M166.53,76.882c1.973,0,4.042-0.589,6.209-1.768c2.166-1.18,3.81-2.854,4.931-5.02v-9.98h-4.526
c-5.105,0-8.858,0.783-11.256,2.35c-2.399,1.566-3.598,3.858-3.598,6.875c0,2.36,0.725,4.207,2.176,5.541
C161.917,76.217,163.938,76.882,166.53,76.882z"/>
<path fill="#00819E" d="M252.289,0c4.487,0,8.451,0.357,11.895,1.074c3.442,0.716,6.518,1.653,9.227,2.814l-5.049,11.954
c-2.127-0.813-4.313-1.402-6.556-1.771c-2.244-0.367-4.468-0.551-6.673-0.551c-3.637,0-6.238,0.659-7.804,1.974
c-1.567,1.315-2.351,3.501-2.351,6.557v8.24h20.077l-2.031,12.707h-18.046v45.028h-17.866l-0.064-44.74l-13.228-0.289V30.29
h12.939v-8.937c0-4.061,0.966-7.697,2.9-10.908c1.935-3.211,4.797-5.753,8.589-7.63C242.039,0.938,246.719,0,252.289,0z"/>
<path fill="#00819E" d="M308.438,84.997c-2.399,1.547,1.767,3.918-1.636,5.001c-3.405,1.082,6.423-0.54,2.167-0.54
c-8.047,0-5.02-1.52-8.927-5.641c-3.907-4.12-5.859-9.719-5.859-16.799V39.051h-12.941V26.401h12.941V13.056l18.335-2.205v15.551
h19.844l-1.799,12.649h-18.045v27.968c0,3.057,0.696,5.242,2.088,6.558c1.394,1.314,3.617,1.973,6.673,1.973
c2.167,0,4.149-0.261,5.948-0.783c1.799-0.521-11.841-0.48-10.409-1.294L308.438,84.997z"/>
</g>
<g>
<path fill="#00819E" d="M227.004,134.764V54.34h17.974l0.125,65.393h35.808l-1.973,15.03H227.004z"/>
<path fill="#00819E" d="M309.862,109.288c0.619,5.031,2.263,8.638,4.933,10.823c2.668,2.186,5.957,3.277,9.864,3.277
c2.823,0,5.551-0.463,8.182-1.392c2.63-0.928,5.164-2.165,7.602-3.714l7.37,9.98c-2.902,2.477-6.412,4.508-10.532,6.093
c-4.12,1.587-8.811,2.379-14.072,2.379c-7.041,0-12.939-1.403-17.697-4.205c-4.759-2.807-8.337-6.664-10.735-11.578
c-2.398-4.911-3.597-10.56-3.597-16.943c0-6.072,1.16-11.594,3.481-16.565c2.321-4.971,5.715-8.936,10.184-11.896
c4.468-2.958,9.931-4.437,16.392-4.437c5.881,0,10.977,1.256,15.29,3.771c4.313,2.514,7.659,6.131,10.038,10.851
c2.379,4.721,3.569,10.387,3.569,17.001c0,1.045-0.031,2.167-0.088,3.366c-0.058,1.199-0.146,2.263-0.261,3.189H309.862z
M321.235,83.41c-3.288,0-5.938,1.181-7.949,3.54c-2.012,2.36-3.23,6.132-3.656,11.315h22.63
c-0.039-4.487-0.909-8.085-2.611-10.794C327.946,84.765,325.142,83.41,321.235,83.41z"/>
<path fill="#00819E" d="M419.065,131.165c-2.4,1.55-5.301,2.862-8.705,3.946c-3.404,1.082-7.232,1.624-11.487,1.624
c-8.047,0-14.022-2.061-17.93-6.18c-3.908-4.118-5.861-9.718-5.861-16.798V85.789h-12.939V73.14h12.939V59.794l18.335-2.206V73.14
h19.847l-1.799,12.649h-18.048v27.969c0,3.057,0.696,5.242,2.09,6.556c1.394,1.316,3.616,1.974,6.673,1.974
c2.167,0,4.149-0.261,5.949-0.782c1.799-0.522,3.412-1.191,4.844-2.003L419.065,131.165z"/>
<path fill="#00819E" d="M488.692,131.165c-2.397,1.55-5.3,2.862-8.701,3.946c-3.406,1.082-7.234,1.624-11.492,1.624
c-8.044,0-14.021-2.061-17.928-6.18c-3.907-4.118-5.861-9.718-5.861-16.798V85.789h-12.938V73.14h12.938V59.794l18.335-2.206
V73.14h19.847l-1.798,12.649h-18.049v27.969c0,3.057,0.696,5.242,2.091,6.556c1.394,1.316,3.616,1.974,6.673,1.974
c2.167,0,4.149-0.261,5.948-0.782c1.799-0.522,3.413-1.191,4.843-2.003L488.692,131.165z"/>
<path fill="#00819E" d="M518.748,109.288c0.62,5.031,2.266,8.638,4.933,10.823c2.67,2.186,5.957,3.277,9.864,3.277
c2.824,0,5.551-0.463,8.181-1.392c2.631-0.928,5.166-2.165,7.603-3.714l7.37,9.98c-2.9,2.477-6.413,4.508-10.531,6.093
c-4.122,1.587-8.812,2.379-14.072,2.379c-7.042,0-12.938-1.403-17.699-4.205c-4.758-2.807-8.336-6.664-10.733-11.578
c-2.399-4.911-3.598-10.56-3.598-16.943c0-6.072,1.161-11.594,3.481-16.565c2.32-4.971,5.716-8.936,10.184-11.896
c4.47-2.958,9.933-4.437,16.394-4.437c5.879,0,10.974,1.256,15.289,3.771c4.313,2.514,7.658,6.131,10.037,10.851
c2.381,4.721,3.568,10.387,3.568,17.001c0,1.045-0.026,2.167-0.086,3.366c-0.058,1.199-0.146,2.263-0.261,3.189H518.748z
M530.123,83.41c-3.29,0-5.939,1.181-7.949,3.54c-2.014,2.36-3.232,6.132-3.655,11.315h22.627
c-0.037-4.487-0.908-8.085-2.608-10.794C536.833,84.765,534.027,83.41,530.123,83.41z"/>
<path fill="#00819E" d="M573.056,134.764v-12.302h8.125V85.326h-8.125V73.14h22.109l3.367,13.869
c2.204-5.3,5.019-9.245,8.441-11.838c3.425-2.591,7.611-3.888,12.562-3.888c2.091,0,3.948,0.164,5.571,0.493
c1.625,0.329,3.153,0.784,4.583,1.363l-3.423,25.764h-11.488V87.529c-3.48,0.62-6.537,2.506-9.169,5.657
c-2.63,3.155-4.661,7.032-6.091,11.636v17.64h12.184v12.302H573.056z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,65 @@
Title: Lettre n°1
Date: 2025-12-08 10:20
Category: Newsletter
![Logo Craft Letter]({static}/images/craftletter.svg)
## Édito
J'ai découvert le développement logiciel alors que je n'étais qu'un adolescent. Cela m'a passionné, au point que j'en ai fait mon métier, et après plusieurs décennies, je le pratique avec un plaisir toujours renouvelé. Si mon intérêt pour le développement logiciel ne s'est pas émoussé au fil du temps, c'est grâce à la combinaison de plusieurs facteurs.
Tout d'abord, j'apprends en permanence ; être toujours à jour en termes de technologies, d'outils et de pratiques me permet non seulement de rester pertinent, mais aussi de ne pas sombrer dans une routine dont je me lasserais inévitablement. Cela implique de pratiquer une veille technologique constante, sous de multiples formes : lecture d'articles de blogs, de livres, conférences, ateliers, projets personnels... Par chance, tout cela aussi est un plaisir pour moi.
Ensuite, j'essaie de créer des logiciels dont je suis fier : je mets en pratique certaines bonnes idées que je découvre au fil de ma veille, qu'elles soient techniques ou méthodologiques. Et je fais en sorte que les clients et utilisateurs de ces logiciels soient satisfaits.
Enfin, je partage ce que j'apprends : j'ai donné plus d'une centaine de conférences, des dizaines de formations, écrit un livre, et je contribue à l'organisation d'événements : Meetup Software Craft Rennes, SoCraTes Rennes, Breizhcamp... J'apprécie particulièrement l'aspect social du développement, aussi bien le travail en équipe que les échanges au sein de communautés de professionnels.

Améliorer constamment ses compétences et connaissances, se montrer professionnel, et partager : c'est l'essence de l'artisanat logiciel —le _software craft_. Je suis un _crafter_, pour employer un de ces anglicismes dont nous sommes si friands dans ce milieu.
Depuis plus de 15 ans, je partage une partie de mes trouvailles avec les gens qui m'entourent, que ce soient des collègues, ou les gens qui viennent voir mes conférences. La publication d'une newsletter participe de la même envie de diffuser ce qui a retenu mon attention récemment. Mes centres d'intérêt sont assez variés, cela devrait se refléter dans le contenu de la lettre au fil des semaines. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences... Bref, tout ce qui fait le quotidien d'une développeuse ou d'un développeur.
Bonne lecture !
Pascal
## Quitter Github pour Codeberg
[Ce retour d'expérience](https://eldred.fr/blog/forge-migration/) consacré à la migration de dépôts Git de Github vers Codeberg m'a interpelé, car j'ai migré récemment mes projets de Github vers une instance de Gitea (hébergée par Zaclys). Toutefois l'auteur de l'article est allé plus loin que le simple transfert de dépôts Git, car il a aussi migré des pipelines de CI, et les pages hébergées avec Github Pages. Il existe de nombreux scripts pour migrer tous vos dépôts d'un coup, de Github [vers ForgeJo](https://github.com/PatNei/GITHUB2FORGEJO) ou [vers Gitea](https://github.com/situ2001/gitea-bulk-migration). Je suis parti de celui-là, mais il en existe une version améliorée.
Eldred Habert n'est le seul a avoir quitté Github : [les développeurs du langage Zig ont fait de même](https://ziglang.org/news/migrating-from-github-to-codeberg/), mettant en avant la dégradation du service qu'ils ont constaté ces dernières années. Mais cela engendre un risque sur le financement du projet, qui reposait en grande partie sur Github sponsors.
## Gérer les effets de bord en 30 ligne de JavaScript
Comment tester simplement un code qui fait des appels à des services externes, comme une base de données, ou une API ? La mise en oeuvre d'une [architecture functional core - imperative shell, en seulement 30 lignes de Javascript](https://lackofimagination.org/2025/11/managing-side-effects-a-javascript-effect-system-in-30-lines-or-less/), permet de tester la logique métier, sans base de données de test ni mocks.
## L'opérateur pipe se répand
A propos d'emprunts à la programmation fonctionnelle, l'opérateur pipe |> fait partie des [nouveautés de PHP 8.5](https://les-tilleuls.coop/blog/sortie-de-php-8-5-ce-quil-faut-retenir), et il est déjà [disponible en JavaScript](https://blog.openreplay.com/fr/javascript-pipeline-operateur-signifie/) (via Babel). C'est une des choses qui me manque le plus quand je code en Python ou d'autres langages non fonctionnels (le jeu de mot est volontaire). Il change totalement la façon de penser les fonctions, et les rend bien plus simples à lire une fois qu'on a assimilé la syntaxe.
## Pourquoi GCC utilise-t-il des ou exclusifs pour mettre à 0 des registres ?
Dans un [article court et abordable](https://xania.org/202512/01-xor-eax-eax) même quand on y connait pas grand chose en assembleur, Matt Godbolt explique avec beaucoup de pédagogie une optimisation effectuée par GCC. Et si vous vous piquez d'intérêt pour le sujet, il a lancé un calendrier de l'avent dédié à l'[optimisation lors de la compilation de code C ou C++](https://www.youtube.com/playlist?list=PL2HVqYf7If8cY4wLk7JUQ2f0JXY_xMQm2).
## Le calendrier de l'avent HTMHell est de retour
[Ce site](https://htmhell.dev/adventcalendar/?s=hell) initié par [Manuel Matuzović](https://mastodon.social/@matuzo@front-end.social) propose chaque jour jusqu'à Noël un article sur la sécurité, l'accessibilité, l'expérience utilisateur, ou les performances des pages Web.
## Se protéger des attaques supply-chain en Javascript et en Python
[Aikido Safe Chain](https://www.npmjs.com/package/@aikidosec/safe-chain) est un outil qui filtre les demandes de téléchargement de paquets JS et Python, et vérifie si le paquet demandé ne contient pas de code malveillant. A son lancement, en Juillet dernier, il était compatible avec npm, npx, et yarn ; aujourd'hui il fonctionne avec pnpm, pnpx, bun, bunx, mais aussi des outils du monde Python comme uv, pip , ou pip3. Et d'autres gestionnaires de paquets sont prévus.
## Meilleur que JSON
Dans [Meilleur que JSON](https://aloisdeniel.com/blog/better-than-json), Aloïs Deniel explique pourquoi il préfère Protobuf à l'omniprésent JSON : les données sont typées, leur format est vérifié et décrit par un schéma ; la compacité du format binaire améliore les performances. Et l'expérience développeur est améliorée par les générateurs de code. Ce qu'il ne mentionne pas, c'est qu'il existe d'autres formats du même type, comme [Apache Avro](https://avro.apache.org/). JsonToTable fournit une [comparaison détaillée des deux formats](https://jsontotable.org/blog/protobuf/protobuf-vs-avro), et indique dans quels cas d'usage chacun est plus pertinent. Spoiler : aucun des deux n'est meilleur dans tous les cas, mais les deux surpassent JSON.
Et voilà, c'est tout pour cette première lettre !

View file

@ -0,0 +1,70 @@
Title: Lettre n°2
Date: 2025-12-15 09:00
Category: Newsletter
![Logo Craft Letter]({static}/images/craftletter.svg)
## Édito
Alors que je publie ce second numéro de la Craft Letter, il s'est écoulé moins de deux semaines depuis j'ai eu l'idée de publier une newsletter. Dans un état d'esprit marqué par l'Agile des origines —pas l'adoption superfielle qualifiée de [Faux Agile](https://www.zdnet.fr/actualites/le-developpement-agile-plus-une-pretention-qu-une-realite-39870168.htm) 🇫🇷— et par Getting Real —dont je parle ci-dessous—, j'ai publié le premier numéro rapidement, avec le strict nécessaire. Le logo que j'ai créé ne me satisfait pas complètement ? Ce n'est pas grave, j'en changerai plus tard. Je n'avais pas le temps de créer [un site Web](https://www.craftletter.fr) ? Je l'ai fait après la publication du premier numéro. L'essentiel était là : j'avais du contenu intéressant à partager avec vous.
Voici les articles et projets qui m'ont marqué cette semaine, agrémentés parfois de quelques réflexions issues de mon expérience ou des recherches que j'ai faites.
Bonne lecture !

## Bebop
Dans le premier numéro de la Craft Letter était citées des alternatives à Json, telles que Protobuf et Apache Avro. Ce sont des protocoles binaires et typés, donc plus compacts et performants que Json. Le typage permet une expérience développeur sympathique. [Bebop](https://bebop.sh), dont la première version date de 2020, se présente comme une version plus moderne de Protobuf, sorti en 2001 —mais [qui continue d'évoluer](https://protobuf.dev/news/).
## Une comparaison de Go, Rust et Zig
[Sinclair Target compare Go, Rust et Zig](https://sinclairtarget.com/blog/2025/08/thoughts-on-go-vs.-rust-vs.-zig/) 🇬🇧, après quelques quelques mois d'expérimentation sur ces trois langages. Ses réflexions sont intéressantes, bien qu'il n'ait pas pratiqué ces langages dans un cadre professionnel ni sur une longue durée. Il explique notamment ce qui rend Rust si compliqué à apprendre d'après lui : ce ne serait pas la gestion de la mémoire, mais la multplicité des concepts du langage.
## Un algorithme de calcul de date hyper rapide
Ben Joffe est un passionné d'algorithmie, et plus particulièrement d'algorithmes de calcul de date. Il vient de présenter un nouvel algorithme nettement plus rapide que l'état de l'art, pour calculer la date à partir du nombre de jours écoulés depuis le 1er Janvier 1970.
Dans [A Very Fast 64Bit Date Algorithm: 3040% faster](https://www.benjoffe.com/fast-date-64) 🇬🇧, il décrit en détail le fonctionnement de ce nouvel algorithme, les nouveautés qui le distinguent des précédents, et ce qui le rend aussi rapide.
## Pourquoi quitter Github ?
Un billet sur le blog d'[Iroco](https://iroco.co/), un service mail Français éthique, nous explique pourquoi ils pensent qu'[il faut quitter Github](https://blog.iroco.co/pourquoi-quitter-github/) **:** ça parle d'IA générative, de souveraineté numérique, de colonialisme, de techno-autoritarisme...
## Aerospace, un gestionnaire de fenêtre pour MacOS
Si comme moi vous n'appréciez pas le gestionnaire de fenêtres de MacOS, vous serez peut-être intéressé·e par cet article de Seg6 qui explique, selon lui, comment [rendre MacOS supportable](https://seg6.space/posts/making-macos-bearable/) 🇬🇧. Seg6 y présente plusieurs outils pour se passer de la souris dans de nombreux cas, comme Aerospace et Vimium. [Aerospace](https://github.com/nikitabobko/AeroSpace) est inspiré de [i3](https://i3wm.org/), un gestionnaire de fenêtres pour Linux**.** [Vimium](https://vimium.github.io/) est une extension pour navigateur qui permet de naviguer presque uniquement au clavier.
## Retour d'expérience sur Shape-up
Shape-up est une alternative aux méthodes Agiles telles que Scrum, Kanban ou XP. Elle a été imaginée chez [37Signals](https://37signals.com) 🇬🇧, l'entreprise derrière Basecamp, et décrite dans un [livre disponible en ligne](https://basecamp.com/shapeup) 🇬🇧, gratuitement. Ce [résumé](https://www.le-ticket.fr/resume-shape-up-basecamp/2821/) 🇫🇷 vous donnera un aperçu, avant de découvrir le [retour d'expérience de Scale X](https://scalex.dev/blog/2-years-with-shape-up/) 🇬🇧.
Un ouvrage de 37Signals qui m'a beaucoup influencé est [Getting Real](https://basecamp.com/books#gettingreal) 🇬🇧, le premier livre qui décrivait leur façon de travailler, bien avant qu'ils inventent Shape-Up. Les chapitres sont très courts, chacun présentant une idée souvent aussi iconoclaste que radicale, sur des sujets aussi divers que le financement d'une start-up, la priorisation des fonctionnalités, le recrutement, l'UX, etc. Contrairement à Shape-Up, ce n'est pas une méthode. Il s'agit plutôt une collection de trucs et astuces dont chacun peut être adopté indépendamment, et utilisé en complément de Scrum, XP ou autre.
[Rework](https://basecamp.com/books#rework) 🇬🇧, paru quelques années après, reprend une grande partie du contenu de Getting Real, et n'y apporte pas grand chose de nouveau sur le fond.
## Vous n'avez pas besoin de framework CSS
Si vous n'avez pas suivi les évolutions du langage CSS ces dernières années, vous risquez d'être surpris·e par cette analyse de trois sites développés par 37Signals (oui, ceux qui ont créé Shape-up, mais ça n'a rien à voir les articles cités ci-dessus). Dans [Vanilla CSS is all you need](https://www.zolkos.com/2025/12/03/vanilla-css-is-all-you-need) 🇬🇧, Rob Zolkos montre comment 37Signals utilise les fonctionnalités récentes de CSS pour se passer de framework, de pré-processeur, et parfois de JavaScript ou d'images.
La première question qui m'est venue à l'esprit à la lecture de cet article, c'est celle du support de ces fonctionnalités par les navigateurs. Une rapide recherche sur [Can I Use](https://caniuse.com) m'a permis d'identifier depuis quand elles sont supportées par les principaux navigateurs :
- Native [custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) (variables) : 2017
- Native [nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting) : 2023
- [Container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries) : 2023
- [:has()](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) : 2023
- [CSS Layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) : 2022
- [color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) : 2023
- [clamp()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/clamp), [min()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/min), [max()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/max) : 2020
## 100 000 transactions par seconde : l'efficacité déraisonnable de Sqlite
Anders Murphy a réalisé un benchmark dans lequel il montre que [Sqlite peut être d'une efficacité redoutable](https://andersmurphy.com/2025/12/02/100000-tps-over-a-billion-rows-the-unreasonable-effectiveness-of-sqlite.html) 🇬🇧 par rapport aux SGBD traditionnels, du fait de l'absence de communications réseau. Il faut cependant garder à l'esprit que [Sqlite](https://sqlite.org/index.html), bien que ce soit [la base de donnée la plus utilisée au monde](https://sqlite.org/mostdeployed.html) 🇬🇧, n'est pas adaptée à tous les cas d'usage, et n'offre pas exactement les mêmes fonctionnalités. Juste à titre d'exemple, vous n'y trouverez pas d'ID auto-incrémenté.

37
content/pages/index.md Normal file
View file

@ -0,0 +1,37 @@
Title: Accueil
Date: 2025-12-08 10:20
URL:
save_as: index.html
Category: Home
![Logo Craft Letter]({static}/images/craftletter.svg)
La Craft Letter est une newsletter hebdomadaire dans laquelle je partage des articles
issues de ma veille technologique. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences...
Pour savoir qui je suis, ou pourquoi j'écris cette lettre, je vous invite à vous lire l'édito du [premier numéro]({filename}/newsletter/craft-letter-1.md).
<script>
(function (s, e, n, d, er) {
s['Sender'] = er;
s[er] = s[er] || function () {
(s[er].q = s[er].q || []).push(arguments)
}, s[er].l = 1 * new Date();
var a = e.createElement(n),
m = e.getElementsByTagName(n)[0];
a.async = 1;
a.src = d;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://cdn.sender.net/accounts_resources/universal.js', 'sender');
sender('7ce69975e53330')
</script>
<div style="text-align: center" class="sender-form-field" data-sender-form-id="av2kqL"></div>
---
# Archives
* [Lettre n°2]({filename}/newsletter/craft-letter-2.md)
* [Lettre n°1]({filename}/newsletter/craft-letter-1.md)

20
content/robots.txt Normal file
View file

@ -0,0 +1,20 @@
User-agent: GPTBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: Bytespider
Disallow: /
User-agent: PerplexityBot
Disallow: /
User-agent: *
Sitemap: https://www.craftletter.fr/sitemap.xml

13
devbox.json Normal file
View file

@ -0,0 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.16.0/.schema/devbox.schema.json",
"packages": ["python313Packages.pip@latest"],
"shell": {
"init_hook": [
". venv/bin/activate.fish" ],
"scripts": {
"preview": [
"pelican -r -l"
]
}
}
}

93
devbox.lock Normal file
View file

@ -0,0 +1,93 @@
{
"lockfile_version": "1",
"packages": {
"github:NixOS/nixpkgs/nixpkgs-unstable": {
"last_modified": "2025-11-26T06:22:50Z",
"resolved": "github:NixOS/nixpkgs/bb813de6d2241bcb1b5af2d3059f560c66329967?lastModified=1764138170&narHash=sha256-2bCmfCUZyi2yj9FFXYKwsDiaZmizN75cLhI%2FeWmf3tk%3D"
},
"python313Packages.pip@latest": {
"last_modified": "2025-11-23T21:50:36Z",
"resolved": "github:NixOS/nixpkgs/ee09932cedcef15aaf476f9343d1dea2cb77e261#python313Packages.pip",
"source": "devbox-search",
"version": "25.0.1",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/mrzr287djclm6qbgcsq3gnslrz1xq9gk-python3.13-pip-25.0.1",
"default": true
},
{
"name": "man",
"path": "/nix/store/z869269snssh427n3d4ymdbgv6z2s98m-python3.13-pip-25.0.1-man",
"default": true
},
{
"name": "dist",
"path": "/nix/store/1iacclxznnylaldv1r8bbf79l5w6fbzl-python3.13-pip-25.0.1-dist"
}
],
"store_path": "/nix/store/mrzr287djclm6qbgcsq3gnslrz1xq9gk-python3.13-pip-25.0.1"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/vvll99x1khg8v8biyckj3xwb1ca4ppvn-python3.13-pip-25.0.1",
"default": true
},
{
"name": "man",
"path": "/nix/store/m6wzxsnzm3ip6zgmm9cdbpbklz4acgp1-python3.13-pip-25.0.1-man",
"default": true
},
{
"name": "dist",
"path": "/nix/store/whzw332j5np5034pk6jv441bb11cqw3z-python3.13-pip-25.0.1-dist"
}
],
"store_path": "/nix/store/vvll99x1khg8v8biyckj3xwb1ca4ppvn-python3.13-pip-25.0.1"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/qqqsqw0iwdmxn45lr4dk00wh699ny6py-python3.13-pip-25.0.1",
"default": true
},
{
"name": "man",
"path": "/nix/store/qmis4qangig7vrbn4idpmlz5pcjf77l1-python3.13-pip-25.0.1-man",
"default": true
},
{
"name": "dist",
"path": "/nix/store/nzs1j69si6pmj10hqrnv86s6p6n3wqad-python3.13-pip-25.0.1-dist"
}
],
"store_path": "/nix/store/qqqsqw0iwdmxn45lr4dk00wh699ny6py-python3.13-pip-25.0.1"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/6k8ghavfzfpcgs6angp98gy71xh2mrip-python3.13-pip-25.0.1",
"default": true
},
{
"name": "man",
"path": "/nix/store/pm073whyjzrvh7sgjydf72j99ffm10wb-python3.13-pip-25.0.1-man",
"default": true
},
{
"name": "dist",
"path": "/nix/store/6yc20w1yzz150ma4vgcagb3p8kg4ck7c-python3.13-pip-25.0.1-dist"
}
],
"store_path": "/nix/store/6k8ghavfzfpcgs6angp98gy71xh2mrip-python3.13-pip-25.0.1"
}
}
}
}
}

57
output/archives.html Normal file
View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Craft Letter | Archives</title>
<link rel="shortcut icon" type="image/png" href="https://www.craftletter.fr/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="https://www.craftletter.fr/favicon.ico">
<link href="https://www.craftletter.fr/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Craft Letter Full RSS Feed" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="" />
<meta name="author" content="Pascal Le Merrer" />
</head>
<body>
<header>
<nav>
<ul>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content">
<h1>Archives</h1>
<h4 class="date">Dec 2025</h4>
<div class="post archives">
<ul>
<li><a href="https://www.craftletter.fr/lettre-ndeg2.html">Lettre n°2</a></li>
<li><a href="https://www.craftletter.fr/lettre-ndeg1.html">Lettre n°1</a></li>
</ul>
</div>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="http://getpelican.com">Pelican</a>
&middot;
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Craft Letter | Articles by Pascal Le Merrer</title>
<link rel="shortcut icon" type="image/png" href="https://www.craftletter.fr/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="https://www.craftletter.fr/favicon.ico">
<link href="https://www.craftletter.fr/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Craft Letter Full RSS Feed" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="" />
<meta name="author" content="Pascal Le Merrer" />
</head>
<body>
<header>
<nav>
<ul>
<li class="ephemeral selected"><a href="https://www.craftletter.fr/author/pascal-le-merrer.html">Pascal Le Merrer</a></li>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content"> <h4 class="date">15 Dec 2025</h4>
<article class="post">
<h2 class="title">
<a href="https://www.craftletter.fr/lettre-ndeg2.html" rel="bookmark" title="Permanent Link to &quot;Lettre n°2&quot;">Lettre n°2</a>
</h2>
<p><img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"></p>
<h2>Édito</h2>
<p>Alors que je publie ce second numéro de la Craft Letter, il s'est écoulé moins de deux semaines depuis j'ai eu l'idée de publier une newsletter. Dans un état d'esprit marqué par l'Agile des origines —pas l'adoption superfielle qualifiée de <a href="https://www.zdnet.fr/actualites/le-developpement-agile-plus-une-pretention-qu-une-realite-39870168.htm">Faux Agile</a> 🇫🇷— et par Getting Real —dont je parle ci-dessous—, j'ai publié le premier numéro rapidement, avec le strict nécessaire. Le logo que j'ai créé ne me satisfait pas complètement ? Ce n'est pas grave, j'en changerai plus tard. Je n'avais pas le temps de créer <a href="https://www.craftletter.fr">un site Web</a> ? Je l'ai fait après la publication du premier numéro. L'essentiel était là : j'avais du contenu intéressant à partager avec vous.</p>
<p>Voici les articles et projets qui m'ont marqué cette semaine, agrémentés parfois de quelques réflexions issues de mon expérience ou des recherches que j'ai faites.</p>
<p>Bonne lecture !</p>
<p></p>
<h2>Bebop</h2>
<p>Dans le premier numéro de la Craft Letter était citées des alternatives à Json, telles que Protobuf et Apache Avro. Ce sont des protocoles binaires et typés, donc plus compacts et performants que Json. Le typage permet une expérience développeur sympathique. <a href="https://bebop.sh">Bebop</a>, dont la première version date de 2020, se présente comme une version plus moderne de Protobuf, sorti en 2001 —mais <a href="https://protobuf.dev/news/">qui continue d'évoluer</a>.</p>
<h2>Une comparaison de Go, Rust et Zig</h2>
<p><a href="https://sinclairtarget.com/blog/2025/08/thoughts-on-go-vs.-rust-vs.-zig/">Sinclair Target compare Go, Rust et Zig</a> 🇬🇧, après quelques quelques mois d'expérimentation sur ces trois langages. Ses réflexions sont intéressantes, bien qu'il n'ait pas pratiqué ces langages dans un cadre professionnel ni sur une longue durée. Il explique notamment ce qui rend Rust si compliqué à apprendre d'après lui : ce ne serait pas la gestion de la mémoire, mais la multplicité des concepts du langage.</p>
<h2>Un algorithme de calcul de date hyper rapide</h2>
<p>Ben Joffe est un passionné d'algorithmie, et plus particulièrement d'algorithmes de calcul de date. Il vient de présenter un nouvel algorithme nettement plus rapide que l'état de l'art, pour calculer la date à partir du nombre de jours écoulés depuis le 1er Janvier 1970.</p>
<p>Dans <a href="https://www.benjoffe.com/fast-date-64">A Very Fast 64Bit Date Algorithm: 3040% faster</a> 🇬🇧, il décrit en détail le fonctionnement de ce nouvel algorithme, les nouveautés qui le distinguent des précédents, et ce qui le rend aussi rapide.</p>
<h2>Pourquoi quitter Github ?</h2>
<p>Un billet sur le blog d'<a href="https://iroco.co/">Iroco</a>, un service mail Français éthique, nous explique pourquoi ils pensent qu'<a href="https://blog.iroco.co/pourquoi-quitter-github/">il faut quitter Github</a> <strong>:</strong> ça parle d'IA générative, de souveraineté numérique, de colonialisme, de techno-autoritarisme...</p>
<h2>Aerospace, un gestionnaire de fenêtre pour MacOS</h2>
<p>Si comme moi vous n'appréciez pas le gestionnaire de fenêtres de MacOS, vous serez peut-être intéressé·e par cet article de Seg6 qui explique, selon lui, comment <a href="https://seg6.space/posts/making-macos-bearable/">rendre MacOS supportable</a> 🇬🇧. Seg6 y présente plusieurs outils pour se passer de la souris dans de nombreux cas, comme Aerospace et Vimium. <a href="https://github.com/nikitabobko/AeroSpace">Aerospace</a> est inspiré de <a href="https://i3wm.org/">i3</a>, un gestionnaire de fenêtres pour Linux<strong>.</strong> <a href="https://vimium.github.io/">Vimium</a> est une extension pour navigateur qui permet de naviguer presque uniquement au clavier.</p>
<h2>Retour d'expérience sur Shape-up</h2>
<p>Shape-up est une alternative aux méthodes Agiles telles que Scrum, Kanban ou XP. Elle a été imaginée chez <a href="https://37signals.com">37Signals</a> 🇬🇧, l'entreprise derrière Basecamp, et décrite dans un <a href="https://basecamp.com/shapeup">livre disponible en ligne</a> 🇬🇧, gratuitement. Ce <a href="https://www.le-ticket.fr/resume-shape-up-basecamp/2821/">résumé</a> 🇫🇷 vous donnera un aperçu, avant de découvrir le <a href="https://scalex.dev/blog/2-years-with-shape-up/">retour d'expérience de Scale X</a> 🇬🇧.</p>
<p>Un ouvrage de 37Signals qui m'a beaucoup influencé est <a href="https://basecamp.com/books#gettingreal">Getting Real</a> 🇬🇧, le premier livre qui décrivait leur façon de travailler, bien avant qu'ils inventent Shape-Up. Les chapitres sont très courts, chacun présentant une idée souvent aussi iconoclaste que radicale, sur des sujets aussi divers que le financement d'une start-up, la priorisation des fonctionnalités, le recrutement, l'UX, etc. Contrairement à Shape-Up, ce n'est pas une méthode. Il s'agit plutôt une collection de trucs et astuces dont chacun peut être adopté indépendamment, et utilisé en complément de Scrum, XP ou autre.</p>
<p><a href="https://basecamp.com/books#rework">Rework</a> 🇬🇧, paru quelques années après, reprend une grande partie du contenu de Getting Real, et n'y apporte pas grand chose de nouveau sur le fond.</p>
<h2>Vous n'avez pas besoin de framework CSS</h2>
<p>Si vous n'avez pas suivi les évolutions du langage CSS ces dernières années, vous risquez d'être surpris·e par cette analyse de trois sites développés par 37Signals (oui, ceux qui ont créé Shape-up, mais ça n'a rien à voir les articles cités ci-dessus). Dans <a href="https://www.zolkos.com/2025/12/03/vanilla-css-is-all-you-need">Vanilla CSS is all you need</a> 🇬🇧, Rob Zolkos montre comment 37Signals utilise les fonctionnalités récentes de CSS pour se passer de framework, de pré-processeur, et parfois de JavaScript ou d'images.</p>
<p>La première question qui m'est venue à l'esprit à la lecture de cet article, c'est celle du support de ces fonctionnalités par les navigateurs. Une rapide recherche sur <a href="https://caniuse.com">Can I Use</a> m'a permis d'identifier depuis quand elles sont supportées par les principaux navigateurs :</p>
<ul>
<li>Native <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">custom properties</a> (variables) : 2017</li>
<li>Native <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting">nesting</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries">Container queries</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has">:has()</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@layer">CSS Layers</a> : 2022</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix">color-mix()</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/clamp">clamp()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/min">min()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/max">max()</a> : 2020</li>
</ul>
<h2>100 000 transactions par seconde : l'efficacité déraisonnable de Sqlite</h2>
<p>Anders Murphy a réalisé un benchmark dans lequel il montre que <a href="https://andersmurphy.com/2025/12/02/100000-tps-over-a-billion-rows-the-unreasonable-effectiveness-of-sqlite.html">Sqlite peut être d'une efficacité redoutable</a> 🇬🇧 par rapport aux SGBD traditionnels, du fait de l'absence de communications réseau. Il faut cependant garder à l'esprit que <a href="https://sqlite.org/index.html">Sqlite</a>, bien que ce soit <a href="https://sqlite.org/mostdeployed.html">la base de donnée la plus utilisée au monde</a> 🇬🇧, n'est pas adaptée à tous les cas d'usage, et n'offre pas exactement les mêmes fonctionnalités. Juste à titre d'exemple, vous n'y trouverez pas d'ID auto-incrémenté.</p>
<div class="clear"></div>
<div class="info">
<a href="https://www.craftletter.fr/lettre-ndeg2.html">Publié à 09:00</a>
&nbsp;&middot;&nbsp;<a href="https://www.craftletter.fr/category/newsletter.html" rel="tag">Newsletter</a>
</div>
</article> <h4 class="date">08 Dec 2025</h4>
<article class="post">
<h2 class="title">
<a href="https://www.craftletter.fr/lettre-ndeg1.html" rel="bookmark" title="Permanent Link to &quot;Lettre n°1&quot;">Lettre n°1</a>
</h2>
<p><img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"></p>
<h2>Édito</h2>
<p>J'ai découvert le développement logiciel alors que je n'étais qu'un adolescent. Cela m'a passionné, au point que j'en ai fait mon métier, et après plusieurs décennies, je le pratique avec un plaisir toujours renouvelé. Si mon intérêt pour le développement logiciel ne s'est pas émoussé au fil du temps, c'est grâce à la combinaison de plusieurs facteurs.</p>
<p>Tout d'abord, j'apprends en permanence ; être toujours à jour en termes de technologies, d'outils et de pratiques me permet non seulement de rester pertinent, mais aussi de ne pas sombrer dans une routine dont je me lasserais inévitablement. Cela implique de pratiquer une veille technologique constante, sous de multiples formes : lecture d'articles de blogs, de livres, conférences, ateliers, projets personnels... Par chance, tout cela aussi est un plaisir pour moi.</p>
<p>Ensuite, j'essaie de créer des logiciels dont je suis fier : je mets en pratique certaines bonnes idées que je découvre au fil de ma veille, qu'elles soient techniques ou méthodologiques. Et je fais en sorte que les clients et utilisateurs de ces logiciels soient satisfaits.</p>
<p>Enfin, je partage ce que j'apprends : j'ai donné plus d'une centaine de conférences, des dizaines de formations, écrit un livre, et je contribue à l'organisation d'événements : Meetup Software Craft Rennes, SoCraTes Rennes, Breizhcamp... J'apprécie particulièrement l'aspect social du développement, aussi bien le travail en équipe que les échanges au sein de communautés de professionnels.</p>
<p>
Améliorer constamment ses compétences et connaissances, se montrer professionnel, et partager : c'est l'essence de l'artisanat logiciel —le <em>software craft</em>. Je suis un <em>crafter</em>, pour employer un de ces anglicismes dont nous sommes si friands dans ce milieu.</p>
<p>Depuis plus de 15 ans, je partage une partie de mes trouvailles avec les gens qui m'entourent, que ce soient des collègues, ou les gens qui viennent voir mes conférences. La publication d'une newsletter participe de la même envie de diffuser ce qui a retenu mon attention récemment. Mes centres d'intérêt sont assez variés, cela devrait se refléter dans le contenu de la lettre au fil des semaines. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences... Bref, tout ce qui fait le quotidien d'une développeuse ou d'un développeur.</p>
<p>Bonne lecture ! </p>
<p>Pascal </p>
<h2>Quitter Github pour Codeberg</h2>
<p><a href="https://eldred.fr/blog/forge-migration/">Ce retour d'expérience</a> consacré à la migration de dépôts Git de Github vers Codeberg m'a interpelé, car j'ai migré récemment mes projets de Github vers une instance de Gitea (hébergée par Zaclys). Toutefois l'auteur de l'article est allé plus loin que le simple transfert de dépôts Git, car il a aussi migré des pipelines de CI, et les pages hébergées avec Github Pages. Il existe de nombreux scripts pour migrer tous vos dépôts d'un coup, de Github <a href="https://github.com/PatNei/GITHUB2FORGEJO">vers ForgeJo</a> ou <a href="https://github.com/situ2001/gitea-bulk-migration">vers Gitea</a>. Je suis parti de celui-là, mais il en existe une version améliorée.</p>
<p>Eldred Habert n'est le seul a avoir quitté Github : <a href="https://ziglang.org/news/migrating-from-github-to-codeberg/">les développeurs du langage Zig ont fait de même</a>, mettant en avant la dégradation du service qu'ils ont constaté ces dernières années. Mais cela engendre un risque sur le financement du projet, qui reposait en grande partie sur Github sponsors.</p>
<h2>Gérer les effets de bord en 30 ligne de JavaScript</h2>
<p>Comment tester simplement un code qui fait des appels à des services externes, comme une base de données, ou une API ? La mise en oeuvre d'une <a href="https://lackofimagination.org/2025/11/managing-side-effects-a-javascript-effect-system-in-30-lines-or-less/">architecture functional core - imperative shell, en seulement 30 lignes de Javascript</a>, permet de tester la logique métier, sans base de données de test ni mocks.</p>
<h2>L'opérateur pipe se répand</h2>
<p>A propos d'emprunts à la programmation fonctionnelle, l'opérateur pipe |&gt; fait partie des <a href="https://les-tilleuls.coop/blog/sortie-de-php-8-5-ce-quil-faut-retenir">nouveautés de PHP 8.5</a>, et il est déjà <a href="https://blog.openreplay.com/fr/javascript-pipeline-operateur-signifie/">disponible en JavaScript</a> (via Babel). C'est une des choses qui me manque le plus quand je code en Python ou d'autres langages non fonctionnels (le jeu de mot est volontaire). Il change totalement la façon de penser les fonctions, et les rend bien plus simples à lire une fois qu'on a assimilé la syntaxe.</p>
<h2>Pourquoi GCC utilise-t-il des ou exclusifs pour mettre à 0 des registres ?</h2>
<p>Dans un <a href="https://xania.org/202512/01-xor-eax-eax">article court et abordable</a> même quand on y connait pas grand chose en assembleur, Matt Godbolt explique avec beaucoup de pédagogie une optimisation effectuée par GCC. Et si vous vous piquez d'intérêt pour le sujet, il a lancé un calendrier de l'avent dédié à l'<a href="https://www.youtube.com/playlist?list=PL2HVqYf7If8cY4wLk7JUQ2f0JXY_xMQm2">optimisation lors de la compilation de code C ou C++</a>.</p>
<h2>Le calendrier de l'avent HTMHell est de retour</h2>
<p><a href="https://htmhell.dev/adventcalendar/?s=hell">Ce site</a> initié par <a href="https://mastodon.social/@matuzo@front-end.social">Manuel Matuzović</a> propose chaque jour jusqu'à Noël un article sur la sécurité, l'accessibilité, l'expérience utilisateur, ou les performances des pages Web.</p>
<h2>Se protéger des attaques supply-chain en Javascript et en Python</h2>
<p><a href="https://www.npmjs.com/package/@aikidosec/safe-chain">Aikido Safe Chain</a> est un outil qui filtre les demandes de téléchargement de paquets JS et Python, et vérifie si le paquet demandé ne contient pas de code malveillant. A son lancement, en Juillet dernier, il était compatible avec npm, npx, et yarn ; aujourd'hui il fonctionne avec pnpm, pnpx, bun, bunx, mais aussi des outils du monde Python comme uv, pip , ou pip3. Et d'autres gestionnaires de paquets sont prévus.</p>
<h2>Meilleur que JSON</h2>
<p>Dans <a href="https://aloisdeniel.com/blog/better-than-json">Meilleur que JSON</a>, Aloïs Deniel explique pourquoi il préfère Protobuf à l'omniprésent JSON : les données sont typées, leur format est vérifié et décrit par un schéma ; la compacité du format binaire améliore les performances. Et l'expérience développeur est améliorée par les générateurs de code. Ce qu'il ne mentionne pas, c'est qu'il existe d'autres formats du même type, comme <a href="https://avro.apache.org/">Apache Avro</a>. JsonToTable fournit une <a href="https://jsontotable.org/blog/protobuf/protobuf-vs-avro">comparaison détaillée des deux formats</a>, et indique dans quels cas d'usage chacun est plus pertinent. Spoiler : aucun des deux n'est meilleur dans tous les cas, mais les deux surpassent JSON.</p>
<p>Et voilà, c'est tout pour cette première lettre !</p>
<div class="clear"></div>
<div class="info">
<a href="https://www.craftletter.fr/lettre-ndeg1.html">Publié à 10:20</a>
&nbsp;&middot;&nbsp;<a href="https://www.craftletter.fr/category/newsletter.html" rel="tag">Newsletter</a>
</div>
</article>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="http://getpelican.com">Pelican</a>
&middot;
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

50
output/authors.html Normal file
View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Craft Letter - Authors</title>
<link rel="shortcut icon" type="image/png" href="https://www.craftletter.fr/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="https://www.craftletter.fr/favicon.ico">
<link href="https://www.craftletter.fr/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Craft Letter Full RSS Feed" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="" />
<meta name="author" content="Pascal Le Merrer" />
</head>
<body>
<header>
<nav>
<ul>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content"> <h2>Authors on Craft Letter</h2>
<ul>
<li><a href="https://www.craftletter.fr/author/pascal-le-merrer.html">Pascal Le Merrer</a> (2)</li>
</ul>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="http://getpelican.com">Pelican</a>
&middot;
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

50
output/categories.html Normal file
View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Craft Letter - Categories</title>
<link rel="shortcut icon" type="image/png" href="https://www.craftletter.fr/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="https://www.craftletter.fr/favicon.ico">
<link href="https://www.craftletter.fr/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Craft Letter Full RSS Feed" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="" />
<meta name="author" content="Pascal Le Merrer" />
</head>
<body>
<header>
<nav>
<ul>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content"> <h2>Categories on Craft Letter</h2>
<ul>
<li><a href="https://www.craftletter.fr/category/newsletter.html">Newsletter</a> (2)</li>
</ul>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="http://getpelican.com">Pelican</a>
&middot;
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Craft Letter | articles in the "Newsletter" category</title>
<link rel="shortcut icon" type="image/png" href="https://www.craftletter.fr/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="https://www.craftletter.fr/favicon.ico">
<link href="https://www.craftletter.fr/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Craft Letter Full RSS Feed" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="" />
<meta name="author" content="Pascal Le Merrer" />
</head>
<body>
<header>
<nav>
<ul>
<li class="ephemeral selected"><a href="https://www.craftletter.fr/category/newsletter.html">Newsletter</a></li>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content"> <h4 class="date">15 Dec 2025</h4>
<article class="post">
<h2 class="title">
<a href="https://www.craftletter.fr/lettre-ndeg2.html" rel="bookmark" title="Permanent Link to &quot;Lettre n°2&quot;">Lettre n°2</a>
</h2>
<p><img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"></p>
<h2>Édito</h2>
<p>Alors que je publie ce second numéro de la Craft Letter, il s'est écoulé moins de deux semaines depuis j'ai eu l'idée de publier une newsletter. Dans un état d'esprit marqué par l'Agile des origines —pas l'adoption superfielle qualifiée de <a href="https://www.zdnet.fr/actualites/le-developpement-agile-plus-une-pretention-qu-une-realite-39870168.htm">Faux Agile</a> 🇫🇷— et par Getting Real —dont je parle ci-dessous—, j'ai publié le premier numéro rapidement, avec le strict nécessaire. Le logo que j'ai créé ne me satisfait pas complètement ? Ce n'est pas grave, j'en changerai plus tard. Je n'avais pas le temps de créer <a href="https://www.craftletter.fr">un site Web</a> ? Je l'ai fait après la publication du premier numéro. L'essentiel était là : j'avais du contenu intéressant à partager avec vous.</p>
<p>Voici les articles et projets qui m'ont marqué cette semaine, agrémentés parfois de quelques réflexions issues de mon expérience ou des recherches que j'ai faites.</p>
<p>Bonne lecture !</p>
<p></p>
<h2>Bebop</h2>
<p>Dans le premier numéro de la Craft Letter était citées des alternatives à Json, telles que Protobuf et Apache Avro. Ce sont des protocoles binaires et typés, donc plus compacts et performants que Json. Le typage permet une expérience développeur sympathique. <a href="https://bebop.sh">Bebop</a>, dont la première version date de 2020, se présente comme une version plus moderne de Protobuf, sorti en 2001 —mais <a href="https://protobuf.dev/news/">qui continue d'évoluer</a>.</p>
<h2>Une comparaison de Go, Rust et Zig</h2>
<p><a href="https://sinclairtarget.com/blog/2025/08/thoughts-on-go-vs.-rust-vs.-zig/">Sinclair Target compare Go, Rust et Zig</a> 🇬🇧, après quelques quelques mois d'expérimentation sur ces trois langages. Ses réflexions sont intéressantes, bien qu'il n'ait pas pratiqué ces langages dans un cadre professionnel ni sur une longue durée. Il explique notamment ce qui rend Rust si compliqué à apprendre d'après lui : ce ne serait pas la gestion de la mémoire, mais la multplicité des concepts du langage.</p>
<h2>Un algorithme de calcul de date hyper rapide</h2>
<p>Ben Joffe est un passionné d'algorithmie, et plus particulièrement d'algorithmes de calcul de date. Il vient de présenter un nouvel algorithme nettement plus rapide que l'état de l'art, pour calculer la date à partir du nombre de jours écoulés depuis le 1er Janvier 1970.</p>
<p>Dans <a href="https://www.benjoffe.com/fast-date-64">A Very Fast 64Bit Date Algorithm: 3040% faster</a> 🇬🇧, il décrit en détail le fonctionnement de ce nouvel algorithme, les nouveautés qui le distinguent des précédents, et ce qui le rend aussi rapide.</p>
<h2>Pourquoi quitter Github ?</h2>
<p>Un billet sur le blog d'<a href="https://iroco.co/">Iroco</a>, un service mail Français éthique, nous explique pourquoi ils pensent qu'<a href="https://blog.iroco.co/pourquoi-quitter-github/">il faut quitter Github</a> <strong>:</strong> ça parle d'IA générative, de souveraineté numérique, de colonialisme, de techno-autoritarisme...</p>
<h2>Aerospace, un gestionnaire de fenêtre pour MacOS</h2>
<p>Si comme moi vous n'appréciez pas le gestionnaire de fenêtres de MacOS, vous serez peut-être intéressé·e par cet article de Seg6 qui explique, selon lui, comment <a href="https://seg6.space/posts/making-macos-bearable/">rendre MacOS supportable</a> 🇬🇧. Seg6 y présente plusieurs outils pour se passer de la souris dans de nombreux cas, comme Aerospace et Vimium. <a href="https://github.com/nikitabobko/AeroSpace">Aerospace</a> est inspiré de <a href="https://i3wm.org/">i3</a>, un gestionnaire de fenêtres pour Linux<strong>.</strong> <a href="https://vimium.github.io/">Vimium</a> est une extension pour navigateur qui permet de naviguer presque uniquement au clavier.</p>
<h2>Retour d'expérience sur Shape-up</h2>
<p>Shape-up est une alternative aux méthodes Agiles telles que Scrum, Kanban ou XP. Elle a été imaginée chez <a href="https://37signals.com">37Signals</a> 🇬🇧, l'entreprise derrière Basecamp, et décrite dans un <a href="https://basecamp.com/shapeup">livre disponible en ligne</a> 🇬🇧, gratuitement. Ce <a href="https://www.le-ticket.fr/resume-shape-up-basecamp/2821/">résumé</a> 🇫🇷 vous donnera un aperçu, avant de découvrir le <a href="https://scalex.dev/blog/2-years-with-shape-up/">retour d'expérience de Scale X</a> 🇬🇧.</p>
<p>Un ouvrage de 37Signals qui m'a beaucoup influencé est <a href="https://basecamp.com/books#gettingreal">Getting Real</a> 🇬🇧, le premier livre qui décrivait leur façon de travailler, bien avant qu'ils inventent Shape-Up. Les chapitres sont très courts, chacun présentant une idée souvent aussi iconoclaste que radicale, sur des sujets aussi divers que le financement d'une start-up, la priorisation des fonctionnalités, le recrutement, l'UX, etc. Contrairement à Shape-Up, ce n'est pas une méthode. Il s'agit plutôt une collection de trucs et astuces dont chacun peut être adopté indépendamment, et utilisé en complément de Scrum, XP ou autre.</p>
<p><a href="https://basecamp.com/books#rework">Rework</a> 🇬🇧, paru quelques années après, reprend une grande partie du contenu de Getting Real, et n'y apporte pas grand chose de nouveau sur le fond.</p>
<h2>Vous n'avez pas besoin de framework CSS</h2>
<p>Si vous n'avez pas suivi les évolutions du langage CSS ces dernières années, vous risquez d'être surpris·e par cette analyse de trois sites développés par 37Signals (oui, ceux qui ont créé Shape-up, mais ça n'a rien à voir les articles cités ci-dessus). Dans <a href="https://www.zolkos.com/2025/12/03/vanilla-css-is-all-you-need">Vanilla CSS is all you need</a> 🇬🇧, Rob Zolkos montre comment 37Signals utilise les fonctionnalités récentes de CSS pour se passer de framework, de pré-processeur, et parfois de JavaScript ou d'images.</p>
<p>La première question qui m'est venue à l'esprit à la lecture de cet article, c'est celle du support de ces fonctionnalités par les navigateurs. Une rapide recherche sur <a href="https://caniuse.com">Can I Use</a> m'a permis d'identifier depuis quand elles sont supportées par les principaux navigateurs :</p>
<ul>
<li>Native <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">custom properties</a> (variables) : 2017</li>
<li>Native <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting">nesting</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries">Container queries</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has">:has()</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@layer">CSS Layers</a> : 2022</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix">color-mix()</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/clamp">clamp()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/min">min()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/max">max()</a> : 2020</li>
</ul>
<h2>100 000 transactions par seconde : l'efficacité déraisonnable de Sqlite</h2>
<p>Anders Murphy a réalisé un benchmark dans lequel il montre que <a href="https://andersmurphy.com/2025/12/02/100000-tps-over-a-billion-rows-the-unreasonable-effectiveness-of-sqlite.html">Sqlite peut être d'une efficacité redoutable</a> 🇬🇧 par rapport aux SGBD traditionnels, du fait de l'absence de communications réseau. Il faut cependant garder à l'esprit que <a href="https://sqlite.org/index.html">Sqlite</a>, bien que ce soit <a href="https://sqlite.org/mostdeployed.html">la base de donnée la plus utilisée au monde</a> 🇬🇧, n'est pas adaptée à tous les cas d'usage, et n'offre pas exactement les mêmes fonctionnalités. Juste à titre d'exemple, vous n'y trouverez pas d'ID auto-incrémenté.</p>
<div class="clear"></div>
<div class="info">
<a href="https://www.craftletter.fr/lettre-ndeg2.html">Publié à 09:00</a>
&nbsp;&middot;&nbsp;<a href="https://www.craftletter.fr/category/newsletter.html" rel="tag">Newsletter</a>
</div>
</article> <h4 class="date">08 Dec 2025</h4>
<article class="post">
<h2 class="title">
<a href="https://www.craftletter.fr/lettre-ndeg1.html" rel="bookmark" title="Permanent Link to &quot;Lettre n°1&quot;">Lettre n°1</a>
</h2>
<p><img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"></p>
<h2>Édito</h2>
<p>J'ai découvert le développement logiciel alors que je n'étais qu'un adolescent. Cela m'a passionné, au point que j'en ai fait mon métier, et après plusieurs décennies, je le pratique avec un plaisir toujours renouvelé. Si mon intérêt pour le développement logiciel ne s'est pas émoussé au fil du temps, c'est grâce à la combinaison de plusieurs facteurs.</p>
<p>Tout d'abord, j'apprends en permanence ; être toujours à jour en termes de technologies, d'outils et de pratiques me permet non seulement de rester pertinent, mais aussi de ne pas sombrer dans une routine dont je me lasserais inévitablement. Cela implique de pratiquer une veille technologique constante, sous de multiples formes : lecture d'articles de blogs, de livres, conférences, ateliers, projets personnels... Par chance, tout cela aussi est un plaisir pour moi.</p>
<p>Ensuite, j'essaie de créer des logiciels dont je suis fier : je mets en pratique certaines bonnes idées que je découvre au fil de ma veille, qu'elles soient techniques ou méthodologiques. Et je fais en sorte que les clients et utilisateurs de ces logiciels soient satisfaits.</p>
<p>Enfin, je partage ce que j'apprends : j'ai donné plus d'une centaine de conférences, des dizaines de formations, écrit un livre, et je contribue à l'organisation d'événements : Meetup Software Craft Rennes, SoCraTes Rennes, Breizhcamp... J'apprécie particulièrement l'aspect social du développement, aussi bien le travail en équipe que les échanges au sein de communautés de professionnels.</p>
<p>
Améliorer constamment ses compétences et connaissances, se montrer professionnel, et partager : c'est l'essence de l'artisanat logiciel —le <em>software craft</em>. Je suis un <em>crafter</em>, pour employer un de ces anglicismes dont nous sommes si friands dans ce milieu.</p>
<p>Depuis plus de 15 ans, je partage une partie de mes trouvailles avec les gens qui m'entourent, que ce soient des collègues, ou les gens qui viennent voir mes conférences. La publication d'une newsletter participe de la même envie de diffuser ce qui a retenu mon attention récemment. Mes centres d'intérêt sont assez variés, cela devrait se refléter dans le contenu de la lettre au fil des semaines. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences... Bref, tout ce qui fait le quotidien d'une développeuse ou d'un développeur.</p>
<p>Bonne lecture ! </p>
<p>Pascal </p>
<h2>Quitter Github pour Codeberg</h2>
<p><a href="https://eldred.fr/blog/forge-migration/">Ce retour d'expérience</a> consacré à la migration de dépôts Git de Github vers Codeberg m'a interpelé, car j'ai migré récemment mes projets de Github vers une instance de Gitea (hébergée par Zaclys). Toutefois l'auteur de l'article est allé plus loin que le simple transfert de dépôts Git, car il a aussi migré des pipelines de CI, et les pages hébergées avec Github Pages. Il existe de nombreux scripts pour migrer tous vos dépôts d'un coup, de Github <a href="https://github.com/PatNei/GITHUB2FORGEJO">vers ForgeJo</a> ou <a href="https://github.com/situ2001/gitea-bulk-migration">vers Gitea</a>. Je suis parti de celui-là, mais il en existe une version améliorée.</p>
<p>Eldred Habert n'est le seul a avoir quitté Github : <a href="https://ziglang.org/news/migrating-from-github-to-codeberg/">les développeurs du langage Zig ont fait de même</a>, mettant en avant la dégradation du service qu'ils ont constaté ces dernières années. Mais cela engendre un risque sur le financement du projet, qui reposait en grande partie sur Github sponsors.</p>
<h2>Gérer les effets de bord en 30 ligne de JavaScript</h2>
<p>Comment tester simplement un code qui fait des appels à des services externes, comme une base de données, ou une API ? La mise en oeuvre d'une <a href="https://lackofimagination.org/2025/11/managing-side-effects-a-javascript-effect-system-in-30-lines-or-less/">architecture functional core - imperative shell, en seulement 30 lignes de Javascript</a>, permet de tester la logique métier, sans base de données de test ni mocks.</p>
<h2>L'opérateur pipe se répand</h2>
<p>A propos d'emprunts à la programmation fonctionnelle, l'opérateur pipe |&gt; fait partie des <a href="https://les-tilleuls.coop/blog/sortie-de-php-8-5-ce-quil-faut-retenir">nouveautés de PHP 8.5</a>, et il est déjà <a href="https://blog.openreplay.com/fr/javascript-pipeline-operateur-signifie/">disponible en JavaScript</a> (via Babel). C'est une des choses qui me manque le plus quand je code en Python ou d'autres langages non fonctionnels (le jeu de mot est volontaire). Il change totalement la façon de penser les fonctions, et les rend bien plus simples à lire une fois qu'on a assimilé la syntaxe.</p>
<h2>Pourquoi GCC utilise-t-il des ou exclusifs pour mettre à 0 des registres ?</h2>
<p>Dans un <a href="https://xania.org/202512/01-xor-eax-eax">article court et abordable</a> même quand on y connait pas grand chose en assembleur, Matt Godbolt explique avec beaucoup de pédagogie une optimisation effectuée par GCC. Et si vous vous piquez d'intérêt pour le sujet, il a lancé un calendrier de l'avent dédié à l'<a href="https://www.youtube.com/playlist?list=PL2HVqYf7If8cY4wLk7JUQ2f0JXY_xMQm2">optimisation lors de la compilation de code C ou C++</a>.</p>
<h2>Le calendrier de l'avent HTMHell est de retour</h2>
<p><a href="https://htmhell.dev/adventcalendar/?s=hell">Ce site</a> initié par <a href="https://mastodon.social/@matuzo@front-end.social">Manuel Matuzović</a> propose chaque jour jusqu'à Noël un article sur la sécurité, l'accessibilité, l'expérience utilisateur, ou les performances des pages Web.</p>
<h2>Se protéger des attaques supply-chain en Javascript et en Python</h2>
<p><a href="https://www.npmjs.com/package/@aikidosec/safe-chain">Aikido Safe Chain</a> est un outil qui filtre les demandes de téléchargement de paquets JS et Python, et vérifie si le paquet demandé ne contient pas de code malveillant. A son lancement, en Juillet dernier, il était compatible avec npm, npx, et yarn ; aujourd'hui il fonctionne avec pnpm, pnpx, bun, bunx, mais aussi des outils du monde Python comme uv, pip , ou pip3. Et d'autres gestionnaires de paquets sont prévus.</p>
<h2>Meilleur que JSON</h2>
<p>Dans <a href="https://aloisdeniel.com/blog/better-than-json">Meilleur que JSON</a>, Aloïs Deniel explique pourquoi il préfère Protobuf à l'omniprésent JSON : les données sont typées, leur format est vérifié et décrit par un schéma ; la compacité du format binaire améliore les performances. Et l'expérience développeur est améliorée par les générateurs de code. Ce qu'il ne mentionne pas, c'est qu'il existe d'autres formats du même type, comme <a href="https://avro.apache.org/">Apache Avro</a>. JsonToTable fournit une <a href="https://jsontotable.org/blog/protobuf/protobuf-vs-avro">comparaison détaillée des deux formats</a>, et indique dans quels cas d'usage chacun est plus pertinent. Spoiler : aucun des deux n'est meilleur dans tous les cas, mais les deux surpassent JSON.</p>
<p>Et voilà, c'est tout pour cette première lettre !</p>
<div class="clear"></div>
<div class="info">
<a href="https://www.craftletter.fr/lettre-ndeg1.html">Publié à 10:20</a>
&nbsp;&middot;&nbsp;<a href="https://www.craftletter.fr/category/newsletter.html" rel="tag">Newsletter</a>
</div>
</article>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="http://getpelican.com">Pelican</a>
&middot;
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

6
output/feeds/all.rss.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Craft Letter</title><link>https://www.craftletter.fr/</link><description/><atom:link href="https://www.craftletter.fr/feeds/all.rss.xml" rel="self"/><lastBuildDate>Mon, 15 Dec 2025 09:00:00 +0100</lastBuildDate><item><title>Lettre n°2</title><link>https://www.craftletter.fr/lettre-ndeg2.html</link><description>&lt;p&gt;&lt;img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Édito&lt;/h2&gt;
&lt;p&gt;Alors que je publie ce second numéro de la Craft Letter, il s'est écoulé moins de deux semaines depuis j'ai eu l'idée de publier une newsletter. Dans un état d'esprit marqué par l'Agile des origines —pas l'adoption superfielle qualifiée de &lt;a href="https://www.zdnet.fr/actualites/le-developpement-agile-plus-une-pretention-qu-une-realite-39870168.htm"&gt;Faux Agile&lt;/a&gt; 🇫🇷— et par Getting Real —dont je parle …&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Pascal Le Merrer</dc:creator><pubDate>Mon, 15 Dec 2025 09:00:00 +0100</pubDate><guid>tag:www.craftletter.fr,2025-12-15:/lettre-ndeg2.html</guid><category>Newsletter</category></item><item><title>Lettre n°1</title><link>https://www.craftletter.fr/lettre-ndeg1.html</link><description>&lt;p&gt;&lt;img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Édito&lt;/h2&gt;
&lt;p&gt;J'ai découvert le développement logiciel alors que je n'étais qu'un adolescent. Cela m'a passionné, au point que j'en ai fait mon métier, et après plusieurs décennies, je le pratique avec un plaisir toujours renouvelé. Si mon intérêt pour le développement logiciel ne s'est pas émoussé au fil du temps …&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Pascal Le Merrer</dc:creator><pubDate>Mon, 08 Dec 2025 10:20:00 +0100</pubDate><guid>tag:www.craftletter.fr,2025-12-08:/lettre-ndeg1.html</guid><category>Newsletter</category></item></channel></rss>

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="629.69px" height="136.735px" viewBox="0 0 629.69 136.735" enable-background="new 0 0 629.69 136.735"
xml:space="preserve">
<g>
<g>
<path fill="#00819E" d="M37.601,5.57c5.764,0,10.667,0.764,14.709,2.292c4.042,1.529,7.746,3.685,11.112,6.47L54.079,25.59
c-2.205-1.818-4.642-3.259-7.311-4.323c-2.669-1.064-5.571-1.596-8.704-1.596c-3.365,0-6.441,0.958-9.226,2.873
c-2.785,1.914-5.01,4.932-6.673,9.051c-1.664,4.12-2.495,9.468-2.495,16.044c0,9.671,1.75,16.702,5.25,21.092
s7.999,6.585,13.491,6.585c4.023,0,7.37-0.754,10.038-2.264c2.67-1.508,5.126-3.152,7.37-4.932l8.646,11.024
c-3.017,2.979-6.731,5.533-11.141,7.66c-4.41,2.127-9.729,3.192-15.957,3.192c-7.273,0-13.722-1.617-19.351-4.847
c-5.628-3.229-10.038-7.986-13.23-14.273C1.596,64.592,0,56.846,0,47.639c0-8.974,1.653-16.595,4.96-22.862
c3.308-6.267,7.805-11.034,13.492-14.303C24.138,7.205,30.521,5.57,37.601,5.57z"/>
<path fill="#00819E" d="M74.387,88.024V75.723h8.123V38.587h-8.123V26.401h22.107l3.365,13.868
c2.206-5.299,5.02-9.244,8.443-11.836c3.423-2.591,7.61-3.888,12.563-3.888c2.088,0,3.945,0.165,5.571,0.493
c1.625,0.329,3.152,0.783,4.583,1.364l-3.423,25.764h-11.489V40.792c-3.481,0.619-6.538,2.504-9.167,5.657
c-2.631,3.154-4.662,7.031-6.093,11.634v17.64h12.186v12.302H74.387z"/>
<path fill="#00819E" d="M195.31,70.269c0,2.32,0.319,4.004,0.958,5.048c0.638,1.044,1.672,1.839,3.104,2.379l-3.771,12.069
c-3.637-0.348-6.702-1.151-9.198-2.408c-2.495-1.257-4.458-3.163-5.889-5.715c-2.36,2.823-5.377,4.923-9.052,6.297
c-3.675,1.372-7.427,2.06-11.257,2.06c-6.344,0-11.401-1.811-15.173-5.427c-3.771-3.616-5.657-8.27-5.657-13.955
c0-6.691,2.62-11.856,7.862-15.493c5.241-3.635,12.621-5.454,22.137-5.454h8.297v-2.321c0-6.304-4.061-9.458-12.185-9.458
c-1.973,0-4.507,0.281-7.601,0.841c-3.096,0.561-6.19,1.364-9.284,2.408l-4.236-12.186c3.984-1.509,8.133-2.649,12.447-3.423
c4.313-0.773,8.171-1.161,11.576-1.161c9.167,0,15.947,1.866,20.338,5.6c4.39,3.733,6.585,9.12,6.585,16.159V70.269z
M166.53,76.882c1.973,0,4.042-0.589,6.209-1.768c2.166-1.18,3.81-2.854,4.931-5.02v-9.98h-4.526
c-5.105,0-8.858,0.783-11.256,2.35c-2.399,1.566-3.598,3.858-3.598,6.875c0,2.36,0.725,4.207,2.176,5.541
C161.917,76.217,163.938,76.882,166.53,76.882z"/>
<path fill="#00819E" d="M252.289,0c4.487,0,8.451,0.357,11.895,1.074c3.442,0.716,6.518,1.653,9.227,2.814l-5.049,11.954
c-2.127-0.813-4.313-1.402-6.556-1.771c-2.244-0.367-4.468-0.551-6.673-0.551c-3.637,0-6.238,0.659-7.804,1.974
c-1.567,1.315-2.351,3.501-2.351,6.557v8.24h20.077l-2.031,12.707h-18.046v45.028h-17.866l-0.064-44.74l-13.228-0.289V30.29
h12.939v-8.937c0-4.061,0.966-7.697,2.9-10.908c1.935-3.211,4.797-5.753,8.589-7.63C242.039,0.938,246.719,0,252.289,0z"/>
<path fill="#00819E" d="M308.438,84.997c-2.399,1.547,1.767,3.918-1.636,5.001c-3.405,1.082,6.423-0.54,2.167-0.54
c-8.047,0-5.02-1.52-8.927-5.641c-3.907-4.12-5.859-9.719-5.859-16.799V39.051h-12.941V26.401h12.941V13.056l18.335-2.205v15.551
h19.844l-1.799,12.649h-18.045v27.968c0,3.057,0.696,5.242,2.088,6.558c1.394,1.314,3.617,1.973,6.673,1.973
c2.167,0,4.149-0.261,5.948-0.783c1.799-0.521-11.841-0.48-10.409-1.294L308.438,84.997z"/>
</g>
<g>
<path fill="#00819E" d="M227.004,134.764V54.34h17.974l0.125,65.393h35.808l-1.973,15.03H227.004z"/>
<path fill="#00819E" d="M309.862,109.288c0.619,5.031,2.263,8.638,4.933,10.823c2.668,2.186,5.957,3.277,9.864,3.277
c2.823,0,5.551-0.463,8.182-1.392c2.63-0.928,5.164-2.165,7.602-3.714l7.37,9.98c-2.902,2.477-6.412,4.508-10.532,6.093
c-4.12,1.587-8.811,2.379-14.072,2.379c-7.041,0-12.939-1.403-17.697-4.205c-4.759-2.807-8.337-6.664-10.735-11.578
c-2.398-4.911-3.597-10.56-3.597-16.943c0-6.072,1.16-11.594,3.481-16.565c2.321-4.971,5.715-8.936,10.184-11.896
c4.468-2.958,9.931-4.437,16.392-4.437c5.881,0,10.977,1.256,15.29,3.771c4.313,2.514,7.659,6.131,10.038,10.851
c2.379,4.721,3.569,10.387,3.569,17.001c0,1.045-0.031,2.167-0.088,3.366c-0.058,1.199-0.146,2.263-0.261,3.189H309.862z
M321.235,83.41c-3.288,0-5.938,1.181-7.949,3.54c-2.012,2.36-3.23,6.132-3.656,11.315h22.63
c-0.039-4.487-0.909-8.085-2.611-10.794C327.946,84.765,325.142,83.41,321.235,83.41z"/>
<path fill="#00819E" d="M419.065,131.165c-2.4,1.55-5.301,2.862-8.705,3.946c-3.404,1.082-7.232,1.624-11.487,1.624
c-8.047,0-14.022-2.061-17.93-6.18c-3.908-4.118-5.861-9.718-5.861-16.798V85.789h-12.939V73.14h12.939V59.794l18.335-2.206V73.14
h19.847l-1.799,12.649h-18.048v27.969c0,3.057,0.696,5.242,2.09,6.556c1.394,1.316,3.616,1.974,6.673,1.974
c2.167,0,4.149-0.261,5.949-0.782c1.799-0.522,3.412-1.191,4.844-2.003L419.065,131.165z"/>
<path fill="#00819E" d="M488.692,131.165c-2.397,1.55-5.3,2.862-8.701,3.946c-3.406,1.082-7.234,1.624-11.492,1.624
c-8.044,0-14.021-2.061-17.928-6.18c-3.907-4.118-5.861-9.718-5.861-16.798V85.789h-12.938V73.14h12.938V59.794l18.335-2.206
V73.14h19.847l-1.798,12.649h-18.049v27.969c0,3.057,0.696,5.242,2.091,6.556c1.394,1.316,3.616,1.974,6.673,1.974
c2.167,0,4.149-0.261,5.948-0.782c1.799-0.522,3.413-1.191,4.843-2.003L488.692,131.165z"/>
<path fill="#00819E" d="M518.748,109.288c0.62,5.031,2.266,8.638,4.933,10.823c2.67,2.186,5.957,3.277,9.864,3.277
c2.824,0,5.551-0.463,8.181-1.392c2.631-0.928,5.166-2.165,7.603-3.714l7.37,9.98c-2.9,2.477-6.413,4.508-10.531,6.093
c-4.122,1.587-8.812,2.379-14.072,2.379c-7.042,0-12.938-1.403-17.699-4.205c-4.758-2.807-8.336-6.664-10.733-11.578
c-2.399-4.911-3.598-10.56-3.598-16.943c0-6.072,1.161-11.594,3.481-16.565c2.32-4.971,5.716-8.936,10.184-11.896
c4.47-2.958,9.933-4.437,16.394-4.437c5.879,0,10.974,1.256,15.289,3.771c4.313,2.514,7.658,6.131,10.037,10.851
c2.381,4.721,3.568,10.387,3.568,17.001c0,1.045-0.026,2.167-0.086,3.366c-0.058,1.199-0.146,2.263-0.261,3.189H518.748z
M530.123,83.41c-3.29,0-5.939,1.181-7.949,3.54c-2.014,2.36-3.232,6.132-3.655,11.315h22.627
c-0.037-4.487-0.908-8.085-2.608-10.794C536.833,84.765,534.027,83.41,530.123,83.41z"/>
<path fill="#00819E" d="M573.056,134.764v-12.302h8.125V85.326h-8.125V73.14h22.109l3.367,13.869
c2.204-5.3,5.019-9.245,8.441-11.838c3.425-2.591,7.611-3.888,12.562-3.888c2.091,0,3.948,0.164,5.571,0.493
c1.625,0.329,3.153,0.784,4.583,1.363l-3.423,25.764h-11.488V87.529c-3.48,0.62-6.537,2.506-9.169,5.657
c-2.63,3.155-4.661,7.032-6.091,11.636v17.64h12.184v12.302H573.056z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

75
output/index.html Normal file
View file

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width,initial-scale=1" name="viewport"/>
<title>Craft Letter | Accueil</title>
<link href="https://www.craftletter.fr/favicon.png" rel="shortcut icon" type="image/png"/>
<link href="https://www.craftletter.fr/favicon.ico" rel="shortcut icon" type="image/x-icon"/>
<link href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate" title="Craft Letter Full RSS Feed" type="application/rss+xml">
<link href="https://www.craftletter.fr/theme/css/screen.css" rel="stylesheet" type="text/css">
<link href="https://www.craftletter.fr/theme/css/pygments.css" rel="stylesheet" type="text/css"/>
<link href="https://www.craftletter.fr/theme/css/print.css" media="print" rel="stylesheet" type="text/css"/>
<meta content="Pelican" name="generator">
<meta content="" name="description"/>
<meta content="Pascal Le Merrer" name="author"/>
</meta></link></link><link href="https://www.craftletter.fr/index.html" rel="canonical"/><script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Craft Letter", "item": "https://www.craftletter.fr"}, {"@type": "ListItem", "position": 2, "name": "Index", "item": "https://www.craftletter.fr/index.html"}]}</script><script type="application/ld+json">{"@context": "https://schema.org", "@type": "Article", "author": {"@type": "Person", "name": "Pascal Le Merrer"}, "publisher": {"@type": "Organization", "name": "Craft Letter"}, "headline": "Accueil", "about": "Home", "datePublished": "2025-12-08 10:20"}</script></head>
<body>
<header>
<nav>
<ul>
<!-- -->
<!-- <li class="selected"><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li class="selected"><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content">
<div class="page">
<h1>Accueil</h1>
<p><img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"/></p>
<p>La Craft Letter est une newsletter hebdomadaire dans laquelle je partage des articles
issues de ma veille technologique. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences...</p>
<p>Pour savoir qui je suis, ou pourquoi j'écris cette lettre, je vous invite à vous lire l'édito du <a href="https://www.craftletter.fr/lettre-ndeg1.html">premier numéro</a>.</p>
<script>
(function (s, e, n, d, er) {
s['Sender'] = er;
s[er] = s[er] || function () {
(s[er].q = s[er].q || []).push(arguments)
}, s[er].l = 1 * new Date();
var a = e.createElement(n),
m = e.getElementsByTagName(n)[0];
a.async = 1;
a.src = d;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://cdn.sender.net/accounts_resources/universal.js', 'sender');
sender('7ce69975e53330')
</script>
<div class="sender-form-field" data-sender-form-id="av2kqL" style="text-align: center"></div>
<hr/>
<h1>Archives</h1>
<ul>
<li><a href="https://www.craftletter.fr/lettre-ndeg2.html">Lettre n°2</a></li>
<li><a href="https://www.craftletter.fr/lettre-ndeg1.html">Lettre n°1</a></li>
</ul>
</div>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
·
Propulsé par <a href="http://getpelican.com">Pelican</a>
·
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</p></footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

84
output/lettre-ndeg1.html Normal file
View file

@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width,initial-scale=1" name="viewport"/>
<title>Craft Letter | Lettre n°1</title>
<link href="https://www.craftletter.fr/favicon.png" rel="shortcut icon" type="image/png"/>
<link href="https://www.craftletter.fr/favicon.ico" rel="shortcut icon" type="image/x-icon"/>
<link href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate" title="Craft Letter Full RSS Feed" type="application/rss+xml">
<link href="https://www.craftletter.fr/theme/css/screen.css" rel="stylesheet" type="text/css">
<link href="https://www.craftletter.fr/theme/css/pygments.css" rel="stylesheet" type="text/css"/>
<link href="https://www.craftletter.fr/theme/css/print.css" media="print" rel="stylesheet" type="text/css"/>
<meta content="Pelican" name="generator">
<meta content="" name="description"/>
<meta content="Pascal Le Merrer" name="author"/>
</meta></link></link><link href="https://www.craftletter.fr/lettre-ndeg1.html" rel="canonical"/><script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Craft Letter", "item": "https://www.craftletter.fr"}, {"@type": "ListItem", "position": 2, "name": "Lettre ndeg1", "item": "https://www.craftletter.fr/lettre-ndeg1.html"}]}</script><script type="application/ld+json">{"@context": "https://schema.org", "@type": "Article", "author": {"@type": "Person", "name": "Pascal Le Merrer"}, "publisher": {"@type": "Organization", "name": "Craft Letter"}, "headline": "Lettre n°1", "about": "Newsletter", "datePublished": "2025-12-08 10:20"}</script></head>
<body>
<header>
<nav>
<ul>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content"> <h4 class="date">08 Dec 2025</h4>
<article class="post">
<h2 class="title">
<a href="https://www.craftletter.fr/lettre-ndeg1.html" rel="bookmark" title='Permanent Link to "Lettre n°1"'>Lettre n°1</a>
</h2>
<p><img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"/></p>
<h2>Édito</h2>
<p>J'ai découvert le développement logiciel alors que je n'étais qu'un adolescent. Cela m'a passionné, au point que j'en ai fait mon métier, et après plusieurs décennies, je le pratique avec un plaisir toujours renouvelé. Si mon intérêt pour le développement logiciel ne s'est pas émoussé au fil du temps, c'est grâce à la combinaison de plusieurs facteurs.</p>
<p>Tout d'abord, j'apprends en permanence ; être toujours à jour en termes de technologies, d'outils et de pratiques me permet non seulement de rester pertinent, mais aussi de ne pas sombrer dans une routine dont je me lasserais inévitablement. Cela implique de pratiquer une veille technologique constante, sous de multiples formes : lecture d'articles de blogs, de livres, conférences, ateliers, projets personnels... Par chance, tout cela aussi est un plaisir pour moi.</p>
<p>Ensuite, j'essaie de créer des logiciels dont je suis fier : je mets en pratique certaines bonnes idées que je découvre au fil de ma veille, qu'elles soient techniques ou méthodologiques. Et je fais en sorte que les clients et utilisateurs de ces logiciels soient satisfaits.</p>
<p>Enfin, je partage ce que j'apprends : j'ai donné plus d'une centaine de conférences, des dizaines de formations, écrit un livre, et je contribue à l'organisation d'événements : Meetup Software Craft Rennes, SoCraTes Rennes, Breizhcamp... J'apprécie particulièrement l'aspect social du développement, aussi bien le travail en équipe que les échanges au sein de communautés de professionnels.</p>
<p>
Améliorer constamment ses compétences et connaissances, se montrer professionnel, et partager : c'est l'essence de l'artisanat logiciel —le <em>software craft</em>. Je suis un <em>crafter</em>, pour employer un de ces anglicismes dont nous sommes si friands dans ce milieu.</p>
<p>Depuis plus de 15 ans, je partage une partie de mes trouvailles avec les gens qui m'entourent, que ce soient des collègues, ou les gens qui viennent voir mes conférences. La publication d'une newsletter participe de la même envie de diffuser ce qui a retenu mon attention récemment. Mes centres d'intérêt sont assez variés, cela devrait se refléter dans le contenu de la lettre au fil des semaines. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences... Bref, tout ce qui fait le quotidien d'une développeuse ou d'un développeur.</p>
<p>Bonne lecture ! </p>
<p>Pascal </p>
<h2>Quitter Github pour Codeberg</h2>
<p><a href="https://eldred.fr/blog/forge-migration/">Ce retour d'expérience</a> consacré à la migration de dépôts Git de Github vers Codeberg m'a interpelé, car j'ai migré récemment mes projets de Github vers une instance de Gitea (hébergée par Zaclys). Toutefois l'auteur de l'article est allé plus loin que le simple transfert de dépôts Git, car il a aussi migré des pipelines de CI, et les pages hébergées avec Github Pages. Il existe de nombreux scripts pour migrer tous vos dépôts d'un coup, de Github <a href="https://github.com/PatNei/GITHUB2FORGEJO">vers ForgeJo</a> ou <a href="https://github.com/situ2001/gitea-bulk-migration">vers Gitea</a>. Je suis parti de celui-là, mais il en existe une version améliorée.</p>
<p>Eldred Habert n'est le seul a avoir quitté Github : <a href="https://ziglang.org/news/migrating-from-github-to-codeberg/">les développeurs du langage Zig ont fait de même</a>, mettant en avant la dégradation du service qu'ils ont constaté ces dernières années. Mais cela engendre un risque sur le financement du projet, qui reposait en grande partie sur Github sponsors.</p>
<h2>Gérer les effets de bord en 30 ligne de JavaScript</h2>
<p>Comment tester simplement un code qui fait des appels à des services externes, comme une base de données, ou une API ? La mise en oeuvre d'une <a href="https://lackofimagination.org/2025/11/managing-side-effects-a-javascript-effect-system-in-30-lines-or-less/">architecture functional core - imperative shell, en seulement 30 lignes de Javascript</a>, permet de tester la logique métier, sans base de données de test ni mocks.</p>
<h2>L'opérateur pipe se répand</h2>
<p>A propos d'emprunts à la programmation fonctionnelle, l'opérateur pipe |&gt; fait partie des <a href="https://les-tilleuls.coop/blog/sortie-de-php-8-5-ce-quil-faut-retenir">nouveautés de PHP 8.5</a>, et il est déjà <a href="https://blog.openreplay.com/fr/javascript-pipeline-operateur-signifie/">disponible en JavaScript</a> (via Babel). C'est une des choses qui me manque le plus quand je code en Python ou d'autres langages non fonctionnels (le jeu de mot est volontaire). Il change totalement la façon de penser les fonctions, et les rend bien plus simples à lire une fois qu'on a assimilé la syntaxe.</p>
<h2>Pourquoi GCC utilise-t-il des ou exclusifs pour mettre à 0 des registres ?</h2>
<p>Dans un <a href="https://xania.org/202512/01-xor-eax-eax">article court et abordable</a> même quand on y connait pas grand chose en assembleur, Matt Godbolt explique avec beaucoup de pédagogie une optimisation effectuée par GCC. Et si vous vous piquez d'intérêt pour le sujet, il a lancé un calendrier de l'avent dédié à l'<a href="https://www.youtube.com/playlist?list=PL2HVqYf7If8cY4wLk7JUQ2f0JXY_xMQm2">optimisation lors de la compilation de code C ou C++</a>.</p>
<h2>Le calendrier de l'avent HTMHell est de retour</h2>
<p><a href="https://htmhell.dev/adventcalendar/?s=hell">Ce site</a> initié par <a href="https://mastodon.social/@matuzo@front-end.social">Manuel Matuzović</a> propose chaque jour jusqu'à Noël un article sur la sécurité, l'accessibilité, l'expérience utilisateur, ou les performances des pages Web.</p>
<h2>Se protéger des attaques supply-chain en Javascript et en Python</h2>
<p><a href="https://www.npmjs.com/package/@aikidosec/safe-chain">Aikido Safe Chain</a> est un outil qui filtre les demandes de téléchargement de paquets JS et Python, et vérifie si le paquet demandé ne contient pas de code malveillant. A son lancement, en Juillet dernier, il était compatible avec npm, npx, et yarn ; aujourd'hui il fonctionne avec pnpm, pnpx, bun, bunx, mais aussi des outils du monde Python comme uv, pip , ou pip3. Et d'autres gestionnaires de paquets sont prévus.</p>
<h2>Meilleur que JSON</h2>
<p>Dans <a href="https://aloisdeniel.com/blog/better-than-json">Meilleur que JSON</a>, Aloïs Deniel explique pourquoi il préfère Protobuf à l'omniprésent JSON : les données sont typées, leur format est vérifié et décrit par un schéma ; la compacité du format binaire améliore les performances. Et l'expérience développeur est améliorée par les générateurs de code. Ce qu'il ne mentionne pas, c'est qu'il existe d'autres formats du même type, comme <a href="https://avro.apache.org/">Apache Avro</a>. JsonToTable fournit une <a href="https://jsontotable.org/blog/protobuf/protobuf-vs-avro">comparaison détaillée des deux formats</a>, et indique dans quels cas d'usage chacun est plus pertinent. Spoiler : aucun des deux n'est meilleur dans tous les cas, mais les deux surpassent JSON.</p>
<p>Et voilà, c'est tout pour cette première lettre !</p>
<div class="clear"></div>
<div class="info">
<a href="https://www.craftletter.fr/lettre-ndeg1.html">Publié à 10:20</a>
 · <a href="https://www.craftletter.fr/category/newsletter.html" rel="tag">Newsletter</a>
</div>
</article>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
·
Propulsé par <a href="http://getpelican.com">Pelican</a>
·
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</p></footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

92
output/lettre-ndeg2.html Normal file
View file

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width,initial-scale=1" name="viewport"/>
<title>Craft Letter | Lettre n°2</title>
<link href="https://www.craftletter.fr/favicon.png" rel="shortcut icon" type="image/png"/>
<link href="https://www.craftletter.fr/favicon.ico" rel="shortcut icon" type="image/x-icon"/>
<link href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate" title="Craft Letter Full RSS Feed" type="application/rss+xml">
<link href="https://www.craftletter.fr/theme/css/screen.css" rel="stylesheet" type="text/css">
<link href="https://www.craftletter.fr/theme/css/pygments.css" rel="stylesheet" type="text/css"/>
<link href="https://www.craftletter.fr/theme/css/print.css" media="print" rel="stylesheet" type="text/css"/>
<meta content="Pelican" name="generator">
<meta content="" name="description"/>
<meta content="Pascal Le Merrer" name="author"/>
</meta></link></link><link href="https://www.craftletter.fr/lettre-ndeg2.html" rel="canonical"/><script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Craft Letter", "item": "https://www.craftletter.fr"}, {"@type": "ListItem", "position": 2, "name": "Lettre ndeg2", "item": "https://www.craftletter.fr/lettre-ndeg2.html"}]}</script><script type="application/ld+json">{"@context": "https://schema.org", "@type": "Article", "author": {"@type": "Person", "name": "Pascal Le Merrer"}, "publisher": {"@type": "Organization", "name": "Craft Letter"}, "headline": "Lettre n°2", "about": "Newsletter", "datePublished": "2025-12-15 09:00"}</script></head>
<body>
<header>
<nav>
<ul>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content"> <h4 class="date">15 Dec 2025</h4>
<article class="post">
<h2 class="title">
<a href="https://www.craftletter.fr/lettre-ndeg2.html" rel="bookmark" title='Permanent Link to "Lettre n°2"'>Lettre n°2</a>
</h2>
<p><img alt="Logo Craft Letter" src="https://www.craftletter.fr/images/craftletter.svg"/></p>
<h2>Édito</h2>
<p>Alors que je publie ce second numéro de la Craft Letter, il s'est écoulé moins de deux semaines depuis j'ai eu l'idée de publier une newsletter. Dans un état d'esprit marqué par l'Agile des origines —pas l'adoption superfielle qualifiée de <a href="https://www.zdnet.fr/actualites/le-developpement-agile-plus-une-pretention-qu-une-realite-39870168.htm">Faux Agile</a> 🇫🇷— et par Getting Real —dont je parle ci-dessous—, j'ai publié le premier numéro rapidement, avec le strict nécessaire. Le logo que j'ai créé ne me satisfait pas complètement ? Ce n'est pas grave, j'en changerai plus tard. Je n'avais pas le temps de créer <a href="https://www.craftletter.fr">un site Web</a> ? Je l'ai fait après la publication du premier numéro. L'essentiel était là : j'avais du contenu intéressant à partager avec vous.</p>
<p>Voici les articles et projets qui m'ont marqué cette semaine, agrémentés parfois de quelques réflexions issues de mon expérience ou des recherches que j'ai faites.</p>
<p>Bonne lecture !</p>
<p></p>
<h2>Bebop</h2>
<p>Dans le premier numéro de la Craft Letter était citées des alternatives à Json, telles que Protobuf et Apache Avro. Ce sont des protocoles binaires et typés, donc plus compacts et performants que Json. Le typage permet une expérience développeur sympathique. <a href="https://bebop.sh">Bebop</a>, dont la première version date de 2020, se présente comme une version plus moderne de Protobuf, sorti en 2001 —mais <a href="https://protobuf.dev/news/">qui continue d'évoluer</a>.</p>
<h2>Une comparaison de Go, Rust et Zig</h2>
<p><a href="https://sinclairtarget.com/blog/2025/08/thoughts-on-go-vs.-rust-vs.-zig/">Sinclair Target compare Go, Rust et Zig</a> 🇬🇧, après quelques quelques mois d'expérimentation sur ces trois langages. Ses réflexions sont intéressantes, bien qu'il n'ait pas pratiqué ces langages dans un cadre professionnel ni sur une longue durée. Il explique notamment ce qui rend Rust si compliqué à apprendre d'après lui : ce ne serait pas la gestion de la mémoire, mais la multplicité des concepts du langage.</p>
<h2>Un algorithme de calcul de date hyper rapide</h2>
<p>Ben Joffe est un passionné d'algorithmie, et plus particulièrement d'algorithmes de calcul de date. Il vient de présenter un nouvel algorithme nettement plus rapide que l'état de l'art, pour calculer la date à partir du nombre de jours écoulés depuis le 1er Janvier 1970.</p>
<p>Dans <a href="https://www.benjoffe.com/fast-date-64">A Very Fast 64Bit Date Algorithm: 3040% faster</a> 🇬🇧, il décrit en détail le fonctionnement de ce nouvel algorithme, les nouveautés qui le distinguent des précédents, et ce qui le rend aussi rapide.</p>
<h2>Pourquoi quitter Github ?</h2>
<p>Un billet sur le blog d'<a href="https://iroco.co/">Iroco</a>, un service mail Français éthique, nous explique pourquoi ils pensent qu'<a href="https://blog.iroco.co/pourquoi-quitter-github/">il faut quitter Github</a> <strong>:</strong> ça parle d'IA générative, de souveraineté numérique, de colonialisme, de techno-autoritarisme...</p>
<h2>Aerospace, un gestionnaire de fenêtre pour MacOS</h2>
<p>Si comme moi vous n'appréciez pas le gestionnaire de fenêtres de MacOS, vous serez peut-être intéressé·e par cet article de Seg6 qui explique, selon lui, comment <a href="https://seg6.space/posts/making-macos-bearable/">rendre MacOS supportable</a> 🇬🇧. Seg6 y présente plusieurs outils pour se passer de la souris dans de nombreux cas, comme Aerospace et Vimium. <a href="https://github.com/nikitabobko/AeroSpace">Aerospace</a> est inspiré de <a href="https://i3wm.org/">i3</a>, un gestionnaire de fenêtres pour Linux<strong>.</strong> <a href="https://vimium.github.io/">Vimium</a> est une extension pour navigateur qui permet de naviguer presque uniquement au clavier.</p>
<h2>Retour d'expérience sur Shape-up</h2>
<p>Shape-up est une alternative aux méthodes Agiles telles que Scrum, Kanban ou XP. Elle a été imaginée chez <a href="https://37signals.com">37Signals</a> 🇬🇧, l'entreprise derrière Basecamp, et décrite dans un <a href="https://basecamp.com/shapeup">livre disponible en ligne</a> 🇬🇧, gratuitement. Ce <a href="https://www.le-ticket.fr/resume-shape-up-basecamp/2821/">résumé</a> 🇫🇷 vous donnera un aperçu, avant de découvrir le <a href="https://scalex.dev/blog/2-years-with-shape-up/">retour d'expérience de Scale X</a> 🇬🇧.</p>
<p>Un ouvrage de 37Signals qui m'a beaucoup influencé est <a href="https://basecamp.com/books#gettingreal">Getting Real</a> 🇬🇧, le premier livre qui décrivait leur façon de travailler, bien avant qu'ils inventent Shape-Up. Les chapitres sont très courts, chacun présentant une idée souvent aussi iconoclaste que radicale, sur des sujets aussi divers que le financement d'une start-up, la priorisation des fonctionnalités, le recrutement, l'UX, etc. Contrairement à Shape-Up, ce n'est pas une méthode. Il s'agit plutôt une collection de trucs et astuces dont chacun peut être adopté indépendamment, et utilisé en complément de Scrum, XP ou autre.</p>
<p><a href="https://basecamp.com/books#rework">Rework</a> 🇬🇧, paru quelques années après, reprend une grande partie du contenu de Getting Real, et n'y apporte pas grand chose de nouveau sur le fond.</p>
<h2>Vous n'avez pas besoin de framework CSS</h2>
<p>Si vous n'avez pas suivi les évolutions du langage CSS ces dernières années, vous risquez d'être surpris·e par cette analyse de trois sites développés par 37Signals (oui, ceux qui ont créé Shape-up, mais ça n'a rien à voir les articles cités ci-dessus). Dans <a href="https://www.zolkos.com/2025/12/03/vanilla-css-is-all-you-need">Vanilla CSS is all you need</a> 🇬🇧, Rob Zolkos montre comment 37Signals utilise les fonctionnalités récentes de CSS pour se passer de framework, de pré-processeur, et parfois de JavaScript ou d'images.</p>
<p>La première question qui m'est venue à l'esprit à la lecture de cet article, c'est celle du support de ces fonctionnalités par les navigateurs. Une rapide recherche sur <a href="https://caniuse.com">Can I Use</a> m'a permis d'identifier depuis quand elles sont supportées par les principaux navigateurs :</p>
<ul>
<li>Native <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">custom properties</a> (variables) : 2017</li>
<li>Native <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting">nesting</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries">Container queries</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has">:has()</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@layer">CSS Layers</a> : 2022</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix">color-mix()</a> : 2023</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/clamp">clamp()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/min">min()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/max">max()</a> : 2020</li>
</ul>
<h2>100 000 transactions par seconde : l'efficacité déraisonnable de Sqlite</h2>
<p>Anders Murphy a réalisé un benchmark dans lequel il montre que <a href="https://andersmurphy.com/2025/12/02/100000-tps-over-a-billion-rows-the-unreasonable-effectiveness-of-sqlite.html">Sqlite peut être d'une efficacité redoutable</a> 🇬🇧 par rapport aux SGBD traditionnels, du fait de l'absence de communications réseau. Il faut cependant garder à l'esprit que <a href="https://sqlite.org/index.html">Sqlite</a>, bien que ce soit <a href="https://sqlite.org/mostdeployed.html">la base de donnée la plus utilisée au monde</a> 🇬🇧, n'est pas adaptée à tous les cas d'usage, et n'offre pas exactement les mêmes fonctionnalités. Juste à titre d'exemple, vous n'y trouverez pas d'ID auto-incrémenté.</p>
<div class="clear"></div>
<div class="info">
<a href="https://www.craftletter.fr/lettre-ndeg2.html">Publié à 09:00</a>
 · <a href="https://www.craftletter.fr/category/newsletter.html" rel="tag">Newsletter</a>
</div>
</article>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
·
Propulsé par <a href="http://getpelican.com">Pelican</a>
·
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</p></footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

20
output/robots.txt Normal file
View file

@ -0,0 +1,20 @@
User-agent: GPTBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: Bytespider
Disallow: /
User-agent: PerplexityBot
Disallow: /
User-agent: *
Sitemap: https://www.craftletter.fr/sitemap.xml

48
output/tags.html Normal file
View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Craft Letter</title>
<link rel="shortcut icon" type="image/png" href="https://www.craftletter.fr/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="https://www.craftletter.fr/favicon.ico">
<link href="https://www.craftletter.fr/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Craft Letter Full RSS Feed" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="https://www.craftletter.fr/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="" />
<meta name="author" content="Pascal Le Merrer" />
</head>
<body>
<header>
<nav>
<ul>
<!-- -->
<!-- <li><a href="https://www.craftletter.fr/">Home</a></li> -->
<!-- -->
<li><a href="https://www.craftletter.fr/">Accueil</a></li>
</ul>
</nav>
<div class="header_box">
<h1><a href="https://www.craftletter.fr/">Craft Letter</a></h1>
</div>
</header>
<div id="wrapper">
<div id="content"><ul>
</ul>
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="http://getpelican.com">Pelican</a>
&middot;
<a href="https://www.craftletter.fr/feeds/all.rss.xml" rel="alternate">Flux RSS</a>
</footer>
</div>
<div class="clear"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,4 @@
* { background: #fff; }
body { font-family: georgia, times, serif; color: black; }
blockquote { font-style: italic; color: black; }
a:link, a:visited { border-bottom-width: 1px; border-bottom-style: solid; }

View file

@ -0,0 +1,87 @@
/* Solarized Dark
For use with Jekyll and Pygments
http://ethanschoonover.com/solarized
SOLARIZED HEX ROLE
--------- -------- ------------------------------------------
base03 #002b36 background
base01 #586e75 comments / secondary content
base1 #93a1a1 body text / default code / primary content
orange #cb4b16 constants
red #dc322f regex, special keywords
blue #268bd2 reserved keywords
cyan #2aa198 strings, numbers
green #859900 operators, other keywords
*/
.highlight { background-color: #002b36; color: #93a1a1 }
.highlight .c { color: #586e75 } /* Comment */
.highlight .err { color: #93a1a1 } /* Error */
.highlight .g { color: #93a1a1 } /* Generic */
.highlight .k { color: #859900 } /* Keyword */
.highlight .l { color: #93a1a1 } /* Literal */
.highlight .n { color: #93a1a1 } /* Name */
.highlight .o { color: #859900 } /* Operator */
.highlight .x { color: #cb4b16 } /* Other */
.highlight .p { color: #93a1a1 } /* Punctuation */
.highlight .cm { color: #586e75 } /* Comment.Multiline */
.highlight .cp { color: #859900 } /* Comment.Preproc */
.highlight .c1 { color: #586e75 } /* Comment.Single */
.highlight .cs { color: #859900 } /* Comment.Special */
.highlight .gd { color: #2aa198 } /* Generic.Deleted */
.highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */
.highlight .gr { color: #dc322f } /* Generic.Error */
.highlight .gh { color: #cb4b16 } /* Generic.Heading */
.highlight .gi { color: #859900 } /* Generic.Inserted */
.highlight .go { color: #93a1a1 } /* Generic.Output */
.highlight .gp { color: #93a1a1 } /* Generic.Prompt */
.highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #cb4b16 } /* Generic.Subheading */
.highlight .gt { color: #93a1a1 } /* Generic.Traceback */
.highlight .kc { color: #cb4b16 } /* Keyword.Constant */
.highlight .kd { color: #268bd2 } /* Keyword.Declaration */
.highlight .kn { color: #859900 } /* Keyword.Namespace */
.highlight .kp { color: #859900 } /* Keyword.Pseudo */
.highlight .kr { color: #268bd2 } /* Keyword.Reserved */
.highlight .kt { color: #dc322f } /* Keyword.Type */
.highlight .ld { color: #93a1a1 } /* Literal.Date */
.highlight .m { color: #2aa198 } /* Literal.Number */
.highlight .s { color: #2aa198 } /* Literal.String */
.highlight .na { color: #93a1a1 } /* Name.Attribute */
.highlight .nb { color: #B58900 } /* Name.Builtin */
.highlight .nc { color: #268bd2 } /* Name.Class */
.highlight .no { color: #cb4b16 } /* Name.Constant */
.highlight .nd { color: #268bd2 } /* Name.Decorator */
.highlight .ni { color: #cb4b16 } /* Name.Entity */
.highlight .ne { color: #cb4b16 } /* Name.Exception */
.highlight .nf { color: #268bd2 } /* Name.Function */
.highlight .nl { color: #93a1a1 } /* Name.Label */
.highlight .nn { color: #93a1a1 } /* Name.Namespace */
.highlight .nx { color: #93a1a1 } /* Name.Other */
.highlight .py { color: #93a1a1 } /* Name.Property */
.highlight .nt { color: #268bd2 } /* Name.Tag */
.highlight .nv { color: #268bd2 } /* Name.Variable */
.highlight .ow { color: #859900 } /* Operator.Word */
.highlight .w { color: #93a1a1 } /* Text.Whitespace */
.highlight .mf { color: #2aa198 } /* Literal.Number.Float */
.highlight .mh { color: #2aa198 } /* Literal.Number.Hex */
.highlight .mi { color: #2aa198 } /* Literal.Number.Integer */
.highlight .mo { color: #2aa198 } /* Literal.Number.Oct */
.highlight .sb { color: #586e75 } /* Literal.String.Backtick */
.highlight .sc { color: #2aa198 } /* Literal.String.Char */
.highlight .sd { color: #93a1a1 } /* Literal.String.Doc */
.highlight .s2 { color: #2aa198 } /* Literal.String.Double */
.highlight .se { color: #cb4b16 } /* Literal.String.Escape */
.highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */
.highlight .si { color: #2aa198 } /* Literal.String.Interpol */
.highlight .sx { color: #2aa198 } /* Literal.String.Other */
.highlight .sr { color: #dc322f } /* Literal.String.Regex */
.highlight .s1 { color: #2aa198 } /* Literal.String.Single */
.highlight .ss { color: #2aa198 } /* Literal.String.Symbol */
.highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #268bd2 } /* Name.Variable.Class */
.highlight .vg { color: #268bd2 } /* Name.Variable.Global */
.highlight .vi { color: #268bd2 } /* Name.Variable.Instance */
.highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */

356
output/theme/css/screen.css Normal file
View file

@ -0,0 +1,356 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
/* Mockingbird Theme by Nevan Scott nevanscott.com */
/* Modified by Jody Frankowski */
/* Modified by ix5 */
/* Modified by Pascal Le Merrer */
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, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
em {
font-style: italic;
}
strong {
font-weight: bold;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
font-family: Georgia, serif;
font-size: 16px;
line-height: 1.5em;
color: #444;
}
header, #wrapper {
padding: 0 10px;
min-width: 500px;
max-width: 910px;
margin: auto;
}
a {
box-shadow: inset 0 0 0 0 #801515;
color: #801515;
padding: 0 .25rem;
margin: 0 -.25rem;
transition: color .3s ease-in-out, box-shadow .3s ease-in-out;
}
a:hover {
color: #fff;
box-shadow: inset 800px 0 0 0 #801515;
}
ul {
list-style: outside disc;
}
ol {
list-style: outside decimal;
}
h1, h2, h3, h4, h5, h6 {
font-family: sans-serif;
font-weight: bold;
}
h1, h2, h3 {
font-size: 1.5em;
line-height: 1em;
margin: 1em 0;
}
img, p, .post > .highlight, .highlighttable, h4, h5, h6 {
margin-top: 1.2em;
}
blockquote {
margin: 1.5em 1.5em 1.5em .75em;
padding-left: .75em;
border-left: 1px solid #EEE;
}
.date {
color: #CCC;
float: left;
clear: both;
width: 130px;
font-size: 1.5em;
line-height: 1em;
margin: 0 20px 1em 0;
}
.info {
margin-top: 1.3em;
font-family: sans-serif;
text-align: right;
color: #BBB;
}
.info a {
color: inherit;
}
.info a.tags {
background: #CCC;
color: #FFF;
display: inline-block;
padding: 0 .3em;
border: 1px transparent solid;
border-radius: 5px;
margin: 0 0 0.3em 0;
}
.info a.tags:hover {
background: inherit;
color: inherit;
}
.info a.tags.selected {
border: 1px #999 solid;
}
.post {
margin: 0 0 4.5em 150px;
}
.post.archives {
margin-bottom: 1.5em;
margin-left: 160px;
}
.post p {
text-align: justify;
}
.page {
margin: 0 90px;
}
.highlight {
border-radius: 3px;
}
.code > .highlight {
border-radius: 0px 3px 3px 0px;
}
.linenos {
border-radius: 3px 0px 0px 3px;
background-color: #073642;
border-right: 1px solid #00232C;
color: #586E75;
text-shadow: 0px -1px #021014;
}
td.code {
width: 100%;
max-width: 100px;
}
.linenos a {
color: #586E75;
}
img {
border-radius: 0.3em;
max-width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
margin-bottom: 6rem;
}
/*sub and sup stolen from Twitter bootstrap.*/
sub, sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
.post pre, .page pre {
padding: .8em;
font-size: 12px;
font-family: Monospace;
line-height: 1.1em;
overflow: auto;
}
form.inline_edit {
clear: both;
margin: 4.5em 0;
background-color: #DDD;
color: #000;
padding: 20px;
border-radius: 5px;
}
.inline_edit .sub {
color: #888;
white-space: nowrap;
}
.inline_edit label {
float: left;
clear: both;
width: 140px;
margin-right: 20px;
}
.inline_edit .buttons {
display: block;
text-align: right;
}
nav ul {
float: right;
list-style: none;
margin: 0 0 0 3em;
padding: 0;
}
nav li {
float: left;
}
nav a {
color: #801515;
display: block;
padding: 4.5em 10px 10px 10px;
}
nav a:hover {
color: #801515;
text-decoration: underline;
background-color: #d3d3d3;
color: #FFF;
}
nav li.selected a {
background-color: #801515;
color: #FFF;
}
header .header_box {
padding-top: 4.5em;
}
header h1 {
font-size: 1.5em;
line-height: 1em;
margin: 0;
}
header h2 {
font-size: 1em;
margin: .3em 0;
color: #DDD;
}
#content {
margin-top: 3em;
}
.pages {
font-family: sans-serif;
line-height: 2.5em;
margin: 4.5em 0 3em;
background-color: #F9F9F9;
color: #444;
border-radius: 5px;
}
.pages a.next_page {
float: right;
width: 140px;
text-align: center;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
background-color: #EEE;
}
.pages a.prev_page {
float: left;
width: 140px;
text-align: center;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
background-color: #EEE;
}
.pages a {
color: inherit;
border: none;
}
.pages a:hover {
background-color: #DDD;
}
.pages span {
display: block;
margin: 0 160px;
text-align: center;
}
code {
background-color: #F9F2F4;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
box-sizing: border-box;
color: #C7254E;
font-family: Monaco,Menlo,Consolas,"Courier New",monospace;
font-size: 12.6px;
line-height: 18px;
padding-bottom: 2px;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
white-space: nowrap;
}
footer {
font-family: sans-serif;
line-height: 2.5em;
text-align: center;
color: #BBB;
margin: 3em 0;
border: 1px solid #EEE;
border-radius: 5px;
}
footer p { margin: 0; }
.right { float: right; }
.clear { clear: both; }

45
pelicanconf.py Normal file
View file

@ -0,0 +1,45 @@
AUTHOR = 'Pascal Le Merrer'
SITENAME = 'Craft Letter'
SITEURL = "https://www.craftletter.fr"
PATH = "content"
TIMEZONE = 'Europe/Paris'
DEFAULT_LANG = 'fr'
# Feed generation is usually not desired when developing
FEED_ALL_RSS = "feeds/all.rss.xml"
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None
TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
# Blogroll
LINKS = (
("S'abonner à la lettre de veille", "https://craftletter.sender.site"),
)
# Social widget
SOCIAL = (
("Linkedin", "https://www.linkedin.com/in/pascal-le-merrer/"),
("Mastodon", "https://mastodon.social/@pascal_le_merrer"),
)
DEFAULT_PAGINATION = 10
# Uncomment following line if you want document-relative URLs when developing
# RELATIVE_URLS = True
THEME = "themes/blue-penguin"
JINJA_ENVIRONMENT = {
'extensions': ['jinja2.ext.i18n'],
}
STATIC_PATHS = ['images', 'robots.txt']
# SEO
SEO_ENHANCER_SITEMAP_URL = "https://www.craftletter.fr/sitemap.xml"
SEO_ENHANCER = True # SEO enhancer is disabled by default

24
publishconf.py Normal file
View file

@ -0,0 +1,24 @@
# This file is only used if you use `make publish` or
# explicitly specify it as your config file.
import os
import sys
sys.path.append(os.curdir)
from pelicanconf import *
# If your site is available via HTTPS, make sure SITEURL begins with https://
SITEURL = "https://www.craftletter.fr"
RELATIVE_URLS = False
FEED_ALL_RSS = "feeds/all.rss.xml"
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None
DELETE_OUTPUT_DIRECTORY = True
# Following items are often useful when publishing
# DISQUS_SITENAME = ""
# GOOGLE_ANALYTICS = ""

324
seo_report.html Normal file
View file

@ -0,0 +1,324 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>SEO Report</title>
<link href="/Users/pascal/Documents/craft-letter/venv/lib/python3.13/site-packages/pelican/plugins/seo/seo_report/static/seo_report.css" rel="stylesheet" media="all" type="text/css">
</head>
<body>
<header>
<h1>SEO report - Craft Letter</h1>
</header>
<section>
<article>
<div class="article-header">
<h2>lettre-ndeg2.html</h2>
<span>2025-12-15 09:00</span>
</div>
<h3>Page title analysis</h3>
<table>
<tr>
<th class="green">Good</th>
<td>You have declared a title. Nice job!</td>
</tr>
<tr>
<th class="orange">To improve</th>
<td>Your title is too short. The recommended length is 70 characters.</td>
</tr>
</table>
<h3>Page description analysis</h3>
<table>
<tr>
<th class="red">Problems</th>
<td>You need to declare a description to improve SEO.</td>
</tr>
</table>
<h3>Content title analysis</h3>
<table>
<tr>
<th class="red">Problems</th>
<td>You're missing a content title.</td>
</tr>
</table>
<h3>Internal link analysis</h3>
<table>
<tr>
<th class="green">Good</th>
<td>You've included 1 internal links. Nice job!</td>
</tr>
</table>
</article>
<article>
<div class="article-header">
<h2>lettre-ndeg1.html</h2>
<span>2025-12-08 10:20</span>
</div>
<h3>Page title analysis</h3>
<table>
<tr>
<th class="green">Good</th>
<td>You have declared a title. Nice job!</td>
</tr>
<tr>
<th class="orange">To improve</th>
<td>Your title is too short. The recommended length is 70 characters.</td>
</tr>
</table>
<h3>Page description analysis</h3>
<table>
<tr>
<th class="red">Problems</th>
<td>You need to declare a description to improve SEO.</td>
</tr>
</table>
<h3>Content title analysis</h3>
<table>
<tr>
<th class="red">Problems</th>
<td>You're missing a content title.</td>
</tr>
</table>
<h3>Internal link analysis</h3>
<table>
<tr>
<th class="red">Problems</th>
<td>It's better to include internal links.</td>
</tr>
</table>
</article>
<article>
<div class="article-header">
<h2></h2>
<span>2025-12-08 10:20</span>
</div>
<h3>Page title analysis</h3>
<table>
<tr>
<th class="green">Good</th>
<td>You have declared a title. Nice job!</td>
</tr>
<tr>
<th class="orange">To improve</th>
<td>Your title is too short. The recommended length is 70 characters.</td>
</tr>
</table>
<h3>Page description analysis</h3>
<table>
<tr>
<th class="red">Problems</th>
<td>You need to declare a description to improve SEO.</td>
</tr>
</table>
<h3>Content title analysis</h3>
<table>
<tr>
<th class="green">Good</th>
<td>You have declared a content title. Nice job!</td>
</tr>
</table>
<h3>Internal link analysis</h3>
<table>
<tr>
<th class="green">Good</th>
<td>You've included 3 internal links. Nice job!</td>
</tr>
</table>
</article>
</section>
<footer>
<p>Powered by <a href="https://github.com/pelican-plugins/seo">SEO</a>, a Pelican plugin, with much salt.</p>
</footer>
</body>
</html>

145
tasks.py Normal file
View file

@ -0,0 +1,145 @@
import os
import shlex
import shutil
import sys
from invoke import task
from invoke.main import program
from pelican import main as pelican_main
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
OPEN_BROWSER_ON_SERVE = True
SETTINGS_FILE_BASE = "pelicanconf.py"
SETTINGS = {}
SETTINGS.update(DEFAULT_CONFIG)
LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
SETTINGS.update(LOCAL_SETTINGS)
CONFIG = {
"settings_base": SETTINGS_FILE_BASE,
"settings_publish": "publishconf.py",
# Output path. Can be absolute or relative to tasks.py. Default: 'output'
"deploy_path": SETTINGS["OUTPUT_PATH"],
# Host and port for `serve`
"host": "localhost",
"port": 8000,
}
@task
def clean(c):
"""Remove generated files"""
if os.path.isdir(CONFIG["deploy_path"]):
shutil.rmtree(CONFIG["deploy_path"])
os.makedirs(CONFIG["deploy_path"])
@task
def build(c):
"""Build local version of site"""
pelican_run("-s {settings_base}".format(**CONFIG))
@task
def rebuild(c):
"""`build` with the delete switch"""
pelican_run("-d -s {settings_base}".format(**CONFIG))
@task
def regenerate(c):
"""Automatically regenerate site upon file modification"""
pelican_run("-r -s {settings_base}".format(**CONFIG))
@task
def serve(c):
"""Serve site at http://$HOST:$PORT/ (default is localhost:8000)"""
class AddressReuseTCPServer(RootedHTTPServer):
allow_reuse_address = True
server = AddressReuseTCPServer(
CONFIG["deploy_path"],
(CONFIG["host"], CONFIG["port"]),
ComplexHTTPRequestHandler,
)
if OPEN_BROWSER_ON_SERVE:
# Open site in default browser
import webbrowser
webbrowser.open("http://{host}:{port}".format(**CONFIG))
sys.stderr.write("Serving at {host}:{port} ...\n".format(**CONFIG))
server.serve_forever()
@task
def reserve(c):
"""`build`, then `serve`"""
build(c)
serve(c)
@task
def preview(c):
"""Build production version of site"""
pelican_run("-s {settings_publish}".format(**CONFIG))
@task
def livereload(c):
"""Automatically reload browser tab upon file modification."""
from livereload import Server
def cached_build():
cmd = "-s {settings_base} -e CACHE_CONTENT=true LOAD_CONTENT_CACHE=true"
pelican_run(cmd.format(**CONFIG))
cached_build()
server = Server()
theme_path = SETTINGS["THEME"]
watched_globs = [
CONFIG["settings_base"],
f"{theme_path}/templates/**/*.html",
]
content_file_extensions = [".md", ".rst"]
for extension in content_file_extensions:
content_glob = "{}/**/*{}".format(SETTINGS["PATH"], extension)
watched_globs.append(content_glob)
static_file_extensions = [".css", ".js"]
for extension in static_file_extensions:
static_file_glob = f"{theme_path}/static/**/*{extension}"
watched_globs.append(static_file_glob)
for glob in watched_globs:
server.watch(glob, cached_build)
if OPEN_BROWSER_ON_SERVE:
# Open site in default browser
import webbrowser
webbrowser.open("http://{host}:{port}".format(**CONFIG))
server.serve(host=CONFIG["host"], port=CONFIG["port"], root=CONFIG["deploy_path"])
@task
def publish(c):
"""Publish to production via rsync"""
pelican_run("-s {settings_publish}".format(**CONFIG))
c.run(
'rsync --delete --exclude ".DS_Store" -pthrvz -c '
'-e "ssh -p {ssh_port}" '
"{} {ssh_user}@{ssh_host}:{ssh_path}".format(
CONFIG["deploy_path"].rstrip("/") + "/", **CONFIG
)
)
def pelican_run(cmd):
cmd += " " + program.core.remainder # allows to pass-through args to pelican
pelican_main(shlex.split(cmd))

View file

@ -0,0 +1,76 @@
name: Build pelican-themes preview site
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
on:
# Triggers the workflow on push or pull request events but only for the "main" branch
push:
branches: [ "master" ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 1
submodules: recursive
- name: Checkout pelican
uses: actions/checkout@v3
with:
repository: getpelican/pelican
path: _pelican
fetch-depth: 1
- name: Setup python-3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Cache Playwright browsers
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright/
key: ${{ runner.os }}-browsers
- name: Install pelican, plugins and shot-scraper
run: pip install pelican[markdown] pelican-webassets cssmin shot-scraper
- name: Setup shot-scraper
run: shot-scraper install
- name: Generate output
run: python build-theme-previews.py
# Rsync to server hosting pelicanthemes.com
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_PRIVATE_KEY }}
known_hosts: unnecessary
- name: Adding known hosts
run: ssh-keyscan -p 22 -H pelicanthemes.com >> ~/.ssh/known_hosts
- name: Deploy with rsync
run: rsync -avz ./_output/ deploy@pelicanthemes.com:~/roles/caddy/sites/pelicanthemes.com/
# Deploy to Github Pages
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: '_output'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

22
themes/.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db
# Editor temp files #
#####################
.*~
*~
.swp
.*.swp
# Preview artifacts #
#####################
_pelican
_output

272
themes/.gitmodules vendored Normal file
View file

@ -0,0 +1,272 @@
[submodule "pelicanthemes-generator"]
path = pelicanthemes-generator
url = https://github.com/badele/pelicanthemes-generator.git
[submodule "Responsive-Pelican"]
path = Responsive-Pelican
url = https://github.com/ir193/Responsive-Pelican.git
[submodule "hauntr"]
path = hauntr
url = https://github.com/kura/hauntr.git
[submodule "ghastly"]
path = ghastly
url = https://github.com/kura/ghastly.git
[submodule "svbtle"]
path = svbtle
url = https://github.com/wting/pelican-svbtle.git
[submodule "chunk"]
path = chunk
url = https://github.com/tbunnyman/pelican-chunk.git
[submodule "iris"]
path = iris
url = https://github.com/slok/iris.git
[submodule "relapse"]
path = relapse
url = https://github.com/wamonite/relapse.git
[submodule "neat"]
path = neat
url = https://github.com/BYK/pelican-neat.git
[submodule "pelican-mockingbird"]
path = pelican-mockingbird
url = https://github.com/wrl/pelican-mockingbird.git
[submodule "bold"]
path = bold
url = https://github.com/demianbrecht/pelican-bold.git
[submodule "lannisport"]
path = lannisport
url = https://github.com/siovene/lannisport.git
[submodule "Editorial"]
path = Editorial
url = https://gitlab.com/Mimoza/editorial.git
[submodule "fresh"]
path = fresh
url = https://github.com/jsliang/pelican-fresh.git
[submodule "variant-note"]
path = pelican-variant-note
url = https://github.com/mpancorbo/pelican-variant-note.git
[submodule "water-iris"]
path = water-iris
url = https://github.com/jarv/water-iris.git
[submodule "whispers"]
path = whispersTheme
url = https://github.com/deBorn/whispersTheme.git
[submodule "bluegrasshopper"]
path = bluegrasshopper
url = https://github.com/gregseth/pelican-bgh.git
[submodule "pelican-cait"]
path = pelican-cait
url = https://github.com/hdra/pelican-cait.git
[submodule "irfan"]
path = irfan
url = https://github.com/erfaan/pelican-theme-irfan.git
[submodule "svbhack"]
path = svbhack
url = https://github.com/gfidente/pelican-svbhack.git
[submodule "html5-dopetrope"]
path = html5-dopetrope
url = https://github.com/PierrePaul/html5-dopetrope.git
[submodule "plumage"]
path = plumage
url = https://github.com/kdeldycke/plumage.git
[submodule "sundown"]
path = sundown
url = https://github.com/keningle/pelican-sundown.git
[submodule "crowsfoot"]
path = crowsfoot
url = https://github.com/porterjamesj/crowsfoot.git
[submodule "elegant"]
path = elegant
url = https://github.com/Pelican-Elegant/elegant.git
branch = master
[submodule "niu-x2"]
path = niu-x2
url = https://github.com/wilbur-ma/niu-x2.git
[submodule "storm"]
path = storm
url = https://github.com/redVi/storm.git
[submodule "jesuislibre"]
path = jesuislibre
url = https://github.com/badele/pelican-theme-jesuislibre.git
[submodule "sora"]
path = sora
url = https://github.com/if1live/pelican-sora.git
[submodule "pelican-simplegrey"]
path = pelican-simplegrey
url = https://github.com/fle/pelican-simplegrey.git
[submodule "pujangga"]
path = pujangga
url = https://github.com/habibillah/pujangga.git
[submodule "lovers"]
path = lovers
url = https://github.com/chdoig/pelican-bootstrap3-lovers.git
[submodule "BT3-Flat"]
path = BT3-Flat
url = https://github.com/KenMercusLai/BT3-Flat.git
[submodule "voidy-bootstrap"]
path = voidy-bootstrap
url = https://github.com/robulouski/voidy-bootstrap.git
[submodule "burrito"]
path = burrito
url = https://github.com/fly/burrito.git
[submodule "maggner-pelican"]
path = maggner-pelican
url = https://github.com/kplaube/maggner-pelican.git
[submodule "pelican-sober"]
path = pelican-sober
url = https://github.com/fle/pelican-sober.git
[submodule "twenty-html5up"]
path = twenty-html5up
url = https://github.com/frankV/twenty-pelican-html5up.git
[submodule "lazystrap"]
path = lazystrap
url = https://github.com/lazycoder-ru/lazystrap.git
[submodule "pelican-twitchy"]
path = pelican-twitchy
url = https://github.com/ingwinlu/pelican-twitchy.git
[submodule "dev-random3"]
path = dev-random3
url = https://github.com/22decembre/dev-random3.git
[submodule "blue-penguin"]
path = blue-penguin
url = https://github.com/jody-frankowski/blue-penguin.git
[submodule "nikhil-theme"]
path = nikhil-theme
url = https://github.com/gunchu/nikhil-theme.git
[submodule "chameleon"]
path = chameleon
url = https://github.com/yuex/pelican-iliork.git
[submodule "mg"]
path = mg
url = https://github.com/lucachr/pelican-mg.git
[submodule "martin-pelican"]
path = martin-pelican
url = https://github.com/cpaulik/martin-pelican.git
[submodule "nest"]
path = nest
url = https://github.com/molivier/nest.git
[submodule "alchemy"]
path = alchemy
url = https://github.com/nairobilug/pelican-alchemy.git
[submodule "pjport"]
path = pjport
url = https://github.com/xm3ron/pjport.git
[submodule "cid"]
path = cid
url = https://github.com/hdra/Pelican-Cid.git
[submodule "Flex"]
path = Flex
url = https://github.com/alexandrevicenzi/Flex.git
[submodule "octopress"]
path = octopress
url = https://github.com/MrSenko/pelican-octopress-theme
[submodule "smoothie"]
path = smoothie
url = https://github.com/kdheepak89/pelican-smoothie.git
[submodule "material"]
path = material
url = https://github.com/greizgh/pelican-material.git
[submodule "w3-personal-blog"]
path = w3-personal-blog
url = https://github.com/samael500/w3-personal-blog.git
[submodule "free-agent"]
path = free-agent
url = https://github.com/callmefish/pelican-free-agent.git
[submodule "mediumfox"]
path = mediumfox
url = https://github.com/cprieto/pelican-mediumfox.git
[submodule "medius"]
path = medius
url = https://github.com/onuraslan/medius.git
[submodule "materialistic"]
path = materialistic
url = https://github.com/eswarm/materialistic-pelican.git
[submodule "hyde"]
path = hyde
url = https://github.com/jvanz/pelican-hyde.git
[submodule "clean-blog"]
path = clean-blog
url = https://github.com/gilsondev/pelican-clean-blog.git
[submodule "semantic-ui"]
path = semantic-ui
url = https://github.com/ellisonleao/pelican-semantic-ui.git
[submodule "yapeme"]
path = yapeme
url = https://github.com/kplaube/yapeme.git
[submodule "pelican-blue"]
path = pelican-blue
url = https://github.com/Parbhat/pelican-blue.git
[submodule "pelican-hss"]
path = pelican-hss
url = https://github.com/laughk/pelican-hss.git
[submodule "nice-blog"]
path = nice-blog
url = https://github.com/guilherme-toti/nice-blog.git
[submodule "apricot"]
path = apricot
url = https://github.com/livibetter-backup/apricot.git
[submodule "eevee"]
path = eevee
url = https://github.com/kura/eevee.git
[submodule "voce"]
path = voce
url = https://github.com/limbenjamin/voce.git
[submodule "genus"]
path = genus
url = https://github.com/vaiski/genus.git
[submodule "attila"]
path = attila
url = https://github.com/arulrajnet/attila
[submodule "bulrush"]
path = bulrush
url = https://github.com/textbook/bulrush.git
[submodule "jojo"]
path = jojo
url = https://github.com/dokelung/jojo.git
[submodule "supersimple"]
path = supersimple
url = https://github.com/hlrossato/supersimple.git
[submodule "resume"]
path = resume
url = https://github.com/suheb/resume.git
[submodule "taman"]
path = taman
url = https://github.com/karambir/taman.git
[submodule "MinimalXY"]
path = MinimalXY
url = https://github.com/petrnohejl/MinimalXY.git
[submodule "pelican-fh5co-marble"]
path = pelican-fh5co-marble
url = https://github.com/claudio-walser/pelican-fh5co-marble.git
[submodule "grid-focus"]
path = grid-focus
url = https://github.com/oulenz/pelican-grid-focus.git
[submodule "brutalist"]
path = brutalist
url = https://github.com/mamcmanus/brutalist.git
[submodule "simplify-theme"]
path = simplify-theme
url = https://github.com/vuquangtrong/simplify-theme.git
[submodule "buruma"]
path = buruma
url = https://github.com/ivanhercaz/buruma.git
branch = pelican-themes
[submodule "pelican-b-side"]
path = pelican-b-side
url = https://gitlab.com/jhauh/pelican_b_side.git
[submodule "stirring"]
path = stirring
url = https://github.com/hansliu/pelican-stirring.git
[submodule "Papyrus"]
path = Papyrus
url = https://github.com/aleylara/Papyrus
[submodule "blue-penguin-dark"]
path = blue-penguin-dark
url = https://github.com/tcarwash/blue-penguin-dark.git
[submodule "pelican-haerwu-theme"]
path = pelican-haerwu-theme
url = https://github.com/hrw/pelican-haerwu-theme.git
[submodule "Piccolo"]
path = piccolo
url = https://github.com/iamjameswalters/piccolo.git
[submodule "WhatsTheScoop"]
path = WhatsTheScoop
url = git@github.com:SarahRogue81/WhatsTheScoop.git

View file

@ -0,0 +1,17 @@
# Contributors
* [Nevan Scott](https://github.com/nevanscott/Mockingbird) (original author)
* [wrl](http://ghttps://github.com/guikcdithub.com/wrl) (port to pelican, pelican-mockingbird)
* [Jody Frankowski](http://github.com/jody-frankowski) (Blue Penguin)
* [Grimbox](https://github.com/Grimbox)
* [ix5](https://github.com/ix5)
* [dn0](https://github.com/dn0)
* [anhtuann](https://github.com/anhtuann)
* [aperep](https://github.com/aperep)
* [iranzo](https://github.com/iranzo)
* [thetlk](https://github.com/thetlk)
* [SnorlaxYum](https://github.com/SnorlaxYum)
* [guikcd](https://github.com/guikcd)
* [jorgesumle](https://github.com/jorgesumle)
* [crxxn](https://github.com/crxxn)
* [Pascal Le Merrer](https://www.craftletter.fr)

View file

@ -0,0 +1,53 @@
![screenshot](screenshot.png)
# Blue Penguin for pelican
A simple theme for pelican. Solarized pygments. Feeds support.
## Settings
```python
# all the following settings are *optional*
# HTML metadata
SITEDESCRIPTION = ''
# all defaults to True.
DISPLAY_HEADER = True
DISPLAY_FOOTER = True
DISPLAY_HOME = True
DISPLAY_MENU = True
# provided as examples, they make clean urls. used by MENU_INTERNAL_PAGES.
TAGS_URL = 'tags'
TAGS_SAVE_AS = 'tags/index.html'
AUTHORS_URL = 'authors'
AUTHORS_SAVE_AS = 'authors/index.html'
CATEGORIES_URL = 'categories'
CATEGORIES_SAVE_AS = 'categories/index.html'
ARCHIVES_URL = 'archives'
ARCHIVES_SAVE_AS = 'archives/index.html'
# use those if you want pelican standard pages to appear in your menu
MENU_INTERNAL_PAGES = (
('Tags', TAGS_URL, TAGS_SAVE_AS),
('Authors', AUTHORS_URL, AUTHORS_SAVE_AS),
('Categories', CATEGORIES_URL, CATEGORIES_SAVE_AS),
('Archives', ARCHIVES_URL, ARCHIVES_SAVE_AS),
)
# additional menu items
MENUITEMS = (
('GitHub', 'https://github.com/'),
('Linux Kernel', 'https://www.kernel.org/'),
)
```
## How to contribute
Contributions are very welcome. Keep in mind that this theme goal is to be
minimalistic/simple. Contributions will be accepted through Github Pull
Requests. If you dont have a Github account you can suggest me your
changes by email (which you can find on my github profile).
## Contributors
See [CONTRIBUTORS.md](CONTRIBUTORS.md).
## License
Public domain.

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 KiB

View file

@ -0,0 +1,4 @@
* { background: #fff; }
body { font-family: georgia, times, serif; color: black; }
blockquote { font-style: italic; color: black; }
a:link, a:visited { border-bottom-width: 1px; border-bottom-style: solid; }

View file

@ -0,0 +1,87 @@
/* Solarized Dark
For use with Jekyll and Pygments
http://ethanschoonover.com/solarized
SOLARIZED HEX ROLE
--------- -------- ------------------------------------------
base03 #002b36 background
base01 #586e75 comments / secondary content
base1 #93a1a1 body text / default code / primary content
orange #cb4b16 constants
red #dc322f regex, special keywords
blue #268bd2 reserved keywords
cyan #2aa198 strings, numbers
green #859900 operators, other keywords
*/
.highlight { background-color: #002b36; color: #93a1a1 }
.highlight .c { color: #586e75 } /* Comment */
.highlight .err { color: #93a1a1 } /* Error */
.highlight .g { color: #93a1a1 } /* Generic */
.highlight .k { color: #859900 } /* Keyword */
.highlight .l { color: #93a1a1 } /* Literal */
.highlight .n { color: #93a1a1 } /* Name */
.highlight .o { color: #859900 } /* Operator */
.highlight .x { color: #cb4b16 } /* Other */
.highlight .p { color: #93a1a1 } /* Punctuation */
.highlight .cm { color: #586e75 } /* Comment.Multiline */
.highlight .cp { color: #859900 } /* Comment.Preproc */
.highlight .c1 { color: #586e75 } /* Comment.Single */
.highlight .cs { color: #859900 } /* Comment.Special */
.highlight .gd { color: #2aa198 } /* Generic.Deleted */
.highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */
.highlight .gr { color: #dc322f } /* Generic.Error */
.highlight .gh { color: #cb4b16 } /* Generic.Heading */
.highlight .gi { color: #859900 } /* Generic.Inserted */
.highlight .go { color: #93a1a1 } /* Generic.Output */
.highlight .gp { color: #93a1a1 } /* Generic.Prompt */
.highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #cb4b16 } /* Generic.Subheading */
.highlight .gt { color: #93a1a1 } /* Generic.Traceback */
.highlight .kc { color: #cb4b16 } /* Keyword.Constant */
.highlight .kd { color: #268bd2 } /* Keyword.Declaration */
.highlight .kn { color: #859900 } /* Keyword.Namespace */
.highlight .kp { color: #859900 } /* Keyword.Pseudo */
.highlight .kr { color: #268bd2 } /* Keyword.Reserved */
.highlight .kt { color: #dc322f } /* Keyword.Type */
.highlight .ld { color: #93a1a1 } /* Literal.Date */
.highlight .m { color: #2aa198 } /* Literal.Number */
.highlight .s { color: #2aa198 } /* Literal.String */
.highlight .na { color: #93a1a1 } /* Name.Attribute */
.highlight .nb { color: #B58900 } /* Name.Builtin */
.highlight .nc { color: #268bd2 } /* Name.Class */
.highlight .no { color: #cb4b16 } /* Name.Constant */
.highlight .nd { color: #268bd2 } /* Name.Decorator */
.highlight .ni { color: #cb4b16 } /* Name.Entity */
.highlight .ne { color: #cb4b16 } /* Name.Exception */
.highlight .nf { color: #268bd2 } /* Name.Function */
.highlight .nl { color: #93a1a1 } /* Name.Label */
.highlight .nn { color: #93a1a1 } /* Name.Namespace */
.highlight .nx { color: #93a1a1 } /* Name.Other */
.highlight .py { color: #93a1a1 } /* Name.Property */
.highlight .nt { color: #268bd2 } /* Name.Tag */
.highlight .nv { color: #268bd2 } /* Name.Variable */
.highlight .ow { color: #859900 } /* Operator.Word */
.highlight .w { color: #93a1a1 } /* Text.Whitespace */
.highlight .mf { color: #2aa198 } /* Literal.Number.Float */
.highlight .mh { color: #2aa198 } /* Literal.Number.Hex */
.highlight .mi { color: #2aa198 } /* Literal.Number.Integer */
.highlight .mo { color: #2aa198 } /* Literal.Number.Oct */
.highlight .sb { color: #586e75 } /* Literal.String.Backtick */
.highlight .sc { color: #2aa198 } /* Literal.String.Char */
.highlight .sd { color: #93a1a1 } /* Literal.String.Doc */
.highlight .s2 { color: #2aa198 } /* Literal.String.Double */
.highlight .se { color: #cb4b16 } /* Literal.String.Escape */
.highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */
.highlight .si { color: #2aa198 } /* Literal.String.Interpol */
.highlight .sx { color: #2aa198 } /* Literal.String.Other */
.highlight .sr { color: #dc322f } /* Literal.String.Regex */
.highlight .s1 { color: #2aa198 } /* Literal.String.Single */
.highlight .ss { color: #2aa198 } /* Literal.String.Symbol */
.highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #268bd2 } /* Name.Variable.Class */
.highlight .vg { color: #268bd2 } /* Name.Variable.Global */
.highlight .vi { color: #268bd2 } /* Name.Variable.Instance */
.highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */

View file

@ -0,0 +1,356 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
/* Mockingbird Theme by Nevan Scott nevanscott.com */
/* Modified by Jody Frankowski */
/* Modified by ix5 */
/* Modified by Pascal Le Merrer */
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, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
em {
font-style: italic;
}
strong {
font-weight: bold;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
font-family: Georgia, serif;
font-size: 16px;
line-height: 1.5em;
color: #444;
}
header, #wrapper {
padding: 0 10px;
min-width: 500px;
max-width: 910px;
margin: auto;
}
a {
box-shadow: inset 0 0 0 0 #801515;
color: #801515;
padding: 0 .25rem;
margin: 0 -.25rem;
transition: color .3s ease-in-out, box-shadow .3s ease-in-out;
}
a:hover {
color: #fff;
box-shadow: inset 800px 0 0 0 #801515;
}
ul {
list-style: outside disc;
}
ol {
list-style: outside decimal;
}
h1, h2, h3, h4, h5, h6 {
font-family: sans-serif;
font-weight: bold;
}
h1, h2, h3 {
font-size: 1.5em;
line-height: 1em;
margin: 1em 0;
}
img, p, .post > .highlight, .highlighttable, h4, h5, h6 {
margin-top: 1.2em;
}
blockquote {
margin: 1.5em 1.5em 1.5em .75em;
padding-left: .75em;
border-left: 1px solid #EEE;
}
.date {
color: #CCC;
float: left;
clear: both;
width: 130px;
font-size: 1.5em;
line-height: 1em;
margin: 0 20px 1em 0;
}
.info {
margin-top: 1.3em;
font-family: sans-serif;
text-align: right;
color: #BBB;
}
.info a {
color: inherit;
}
.info a.tags {
background: #CCC;
color: #FFF;
display: inline-block;
padding: 0 .3em;
border: 1px transparent solid;
border-radius: 5px;
margin: 0 0 0.3em 0;
}
.info a.tags:hover {
background: inherit;
color: inherit;
}
.info a.tags.selected {
border: 1px #999 solid;
}
.post {
margin: 0 0 4.5em 150px;
}
.post.archives {
margin-bottom: 1.5em;
margin-left: 160px;
}
.post p {
text-align: justify;
}
.page {
margin: 0 90px;
}
.highlight {
border-radius: 3px;
}
.code > .highlight {
border-radius: 0px 3px 3px 0px;
}
.linenos {
border-radius: 3px 0px 0px 3px;
background-color: #073642;
border-right: 1px solid #00232C;
color: #586E75;
text-shadow: 0px -1px #021014;
}
td.code {
width: 100%;
max-width: 100px;
}
.linenos a {
color: #586E75;
}
img {
border-radius: 0.3em;
max-width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
margin-bottom: 6rem;
}
/*sub and sup stolen from Twitter bootstrap.*/
sub, sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
.post pre, .page pre {
padding: .8em;
font-size: 12px;
font-family: Monospace;
line-height: 1.1em;
overflow: auto;
}
form.inline_edit {
clear: both;
margin: 4.5em 0;
background-color: #DDD;
color: #000;
padding: 20px;
border-radius: 5px;
}
.inline_edit .sub {
color: #888;
white-space: nowrap;
}
.inline_edit label {
float: left;
clear: both;
width: 140px;
margin-right: 20px;
}
.inline_edit .buttons {
display: block;
text-align: right;
}
nav ul {
float: right;
list-style: none;
margin: 0 0 0 3em;
padding: 0;
}
nav li {
float: left;
}
nav a {
color: #801515;
display: block;
padding: 4.5em 10px 10px 10px;
}
nav a:hover {
color: #801515;
text-decoration: underline;
background-color: #d3d3d3;
color: #FFF;
}
nav li.selected a {
background-color: #801515;
color: #FFF;
}
header .header_box {
padding-top: 4.5em;
}
header h1 {
font-size: 1.5em;
line-height: 1em;
margin: 0;
}
header h2 {
font-size: 1em;
margin: .3em 0;
color: #DDD;
}
#content {
margin-top: 3em;
}
.pages {
font-family: sans-serif;
line-height: 2.5em;
margin: 4.5em 0 3em;
background-color: #F9F9F9;
color: #444;
border-radius: 5px;
}
.pages a.next_page {
float: right;
width: 140px;
text-align: center;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
background-color: #EEE;
}
.pages a.prev_page {
float: left;
width: 140px;
text-align: center;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
background-color: #EEE;
}
.pages a {
color: inherit;
border: none;
}
.pages a:hover {
background-color: #DDD;
}
.pages span {
display: block;
margin: 0 160px;
text-align: center;
}
code {
background-color: #F9F2F4;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
box-sizing: border-box;
color: #C7254E;
font-family: Monaco,Menlo,Consolas,"Courier New",monospace;
font-size: 12.6px;
line-height: 18px;
padding-bottom: 2px;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
white-space: nowrap;
}
footer {
font-family: sans-serif;
line-height: 2.5em;
text-align: center;
color: #BBB;
margin: 3em 0;
border: 1px solid #EEE;
border-radius: 5px;
}
footer p { margin: 0; }
.right { float: right; }
.clear { clear: both; }

View file

@ -0,0 +1,11 @@
{% if GOOGLE_ANALYTICS %}
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("{{GOOGLE_ANALYTICS}}");
pageTracker._trackPageview();
} catch(err) {}</script>
{% endif %}

View file

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME }} | Archives{% endblock %}
{% block content %}
<h1>Archives</h1>
{# based on http://stackoverflow.com/questions/12764291/jinja2-group-by-month-year #}
{% for year, year_group in dates|groupby('date.year')|reverse %}
{% for month, month_group in year_group|groupby('date.month')|reverse %}
<h4 class="date">{{ (month_group|first).date|strftime('%b %Y') }}</h4>
<div class="post archives">
<ul>
{% for article in month_group %}
<li><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
{% endfor %}
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block head %}
{{ super() }}
{% if article.tags %}
<meta name="keywords" content="{{ article.tags|join(",") }}" />
{% endif %}
{% if article.description %}
<meta name="description" content="{{ article.description }}" />
{% endif %}
{% endblock %}
{% block title %}{{ SITENAME }} | {{ article.title }}{% endblock %}
{% block content %}
{% include "article_stub.html" %}
{% endblock %}

View file

@ -0,0 +1,37 @@
{% if not articles_page or first_article_of_day %}
<h4 class="date">{{ article.date.strftime("%d %b %Y") }}</h4>
{% endif %}
<article class="post">
{% if article.title %}
<h2 class="title">
<a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permanent Link to &quot;{{ article.title }}&quot;">{{ article.title }}</a>
</h2>
{% endif %}
{% if not articles_page %}
{% include "translations.html" %}
{% endif %}
{{ article.content }}
<div class="clear"></div>
<div class="info">
<a href="{{ SITEURL }}/{{ article.url }}">Publié à {{ article.date.strftime("%H:%M") }}</a>
{% if article.category.name != "misc" %}
&nbsp;&middot;&nbsp;<a href="{{ SITEURL }}/{{ article.category.url }}" rel="tag">{{ article.category }}</a>
{% endif %}
{% if article.tags %}
&nbsp;&middot;
{% for t in article.tags %}
&nbsp;<a href="{{ SITEURL }}/{{ t.url }}" class="tags{% if tag and tag.name == t.name %} selected{% endif %}">{{ t }}</a>
{% endfor %}
{% endif %}
</div>
{% if articles_page and DISQUS_SITENAME %}
<a href="{{ SITEURL }}/{{ article.url }}#disqus_thread">Click to read and post comments</a>
{% else %}
{% include "disqus.html" %}
{% endif %}
</article>

View file

@ -0,0 +1,7 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME }} | Articles by {{ author }}{% endblock %}
{% block ephemeral_nav %}
{{ ephemeral_nav_link(author, output_file, True) }}
{% endblock %}

View file

@ -0,0 +1,105 @@
{% macro ephemeral_nav_link(what, where, selected=False) -%}
<li class="ephemeral{% if selected %} selected{% endif %}"><a href="{{ SITEURL }}/{{ where }}">{{what}}</a></li>
{%- endmacro -%}
<!DOCTYPE html>
<html lang="{{ DEFAULT_LANG }}">
<head>
{% block head %}
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{% block title %}{{ SITENAME }}{% endblock title %}</title>
{# favicon #}
<link rel="shortcut icon" type="image/png" href="{{ SITEURL }}/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="{{ SITEURL }}/favicon.ico">
{% if FEED_ALL_ATOM %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Full Atom Feed" />
{% endif %}
{% if FEED_ALL_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Full RSS Feed" />
{% endif %}
{% if FEED_ATOM %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" />
{% endif %}
{% if FEED_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
{% endif %}
{% if CATEGORY_FEED_ATOM and category %}
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_ATOM.format(slug=category.slug) }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Categories Atom Feed" />
{% endif %}
{% if CATEGORY_FEED_RSS and category %}
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_RSS.format(slug=category.slug) }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Categories RSS Feed" />
{% endif %}
{% if TAG_FEED_ATOM and tag %}
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_ATOM.format(slug=tag.slug) }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Tags Atom Feed" />
{% endif %}
{% if TAG_FEED_RSS and tag %}
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_RSS.format(slug=tag.slug) }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Tags RSS Feed" />
{% endif %}
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/screen.css" type="text/css" />
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/pygments.css" type="text/css" />
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/print.css" type="text/css" media="print" />
<meta name="generator" content="Pelican" />
<meta name="description" content="{{ SITEDESCRIPTION }}" />
<meta name="author" content="{{ AUTHOR }}" />
{% endblock head %}
</head>
<body>
{% if DISPLAY_HEADER or DISPLAY_HEADER is not defined %}
<header>
{% if DISPLAY_MENU or DISPLAY_MENU is not defined %}
<nav>
<ul>
{% block ephemeral_nav %}{% endblock %}
<!-- {% if DISPLAY_HOME or DISPLAY_HOME is not defined %} -->
<!-- <li{% if output_file == "index.html" %} class="selected"{% endif %}><a href="{{ SITEURL }}/">Home</a></li> -->
<!-- {% endif %} -->
{% if DISPLAY_PAGES_ON_MENU %}
{% for p in pages %}
<li{% if p == page %} class="selected"{% endif %}><a href="{{ SITEURL }}/{{ p.url }}">{{ p.title }}</a></li>
{% endfor %}
{% endif %}
{% for title, link in MENUITEMS %}
<li><a href="{{ link }}">{{ title }}</a></li>
{% endfor %}
{% for name, link, file in MENU_INTERNAL_PAGES %}
<li{% if output_file == file %} class="selected"{% endif %}><a href="{{ SITEURL }}/{{ link }}">{{ name }}</a></li>
{% endfor %}
</ul>
</nav>
{% endif %}
<div class="header_box">
<h1><a href="{{ SITEURL }}/">{{ SITENAME }}</a></h1>
{% if SITESUBTITLE %}
<h2>{{ SITESUBTITLE }}</h2>
{% endif %}
</div>
</header>
{% endif %}
<div id="wrapper">
<div id="content">
{%- block content -%}{%- endblock %}
{% if DISPLAY_FOOTER or DISPLAY_FOOTER is not defined %}
<div class="clear"></div>
<footer>
<p>
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
&middot;
Propulsé par <a href="http://getpelican.com">Pelican</a>
{% if FEED_ALL_ATOM %}
&middot;
<a href="{{ SITEURL }}/{{ FEED_ALL_ATOM }}" rel="alternate">Atom Feed</a>
{% endif %}
{% if FEED_ALL_RSS %}
&middot;
<a href="{{ SITEURL }}/{{ FEED_ALL_RSS }}" rel="alternate">Flux RSS</a>
{% endif %}
</footer>
{% endif %}
</div>
<div class="clear"></div>
</div>
{% include 'analytics.html' %}
</body>
</html>

View file

@ -0,0 +1,6 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME }} | articles in the "{{ category }}" category{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %}
{% block ephemeral_nav %}
{{ ephemeral_nav_link(category, output_file, True) }}
{% endblock %}

View file

@ -0,0 +1,12 @@
{% if DISQUS_SITENAME %}
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_shortname = '{{ DISQUS_SITENAME }}';
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>
{% endif %}

View file

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME }}{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %}
{% block content %}
{% set date = None %}
{% for article in articles_page.object_list %}
{% if date != article.date.date() %}
{% set first_article_of_day = True %}
{% else %}
{% set first_article_of_day = False %}
{% endif %}
{% set date = article.date.date() %}
{% include "article_stub.html" %}
{% endfor %}
{% include "pagination.html" %}
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME }} | {{ page.title }}{% endblock %}
{% block content %}
<div class="page">
<h1>{{ page.title }}</h1>
{{ page.content }}
</div>
{% endblock %}

View file

@ -0,0 +1,38 @@
{# Use PAGINATION_PATTERNS or pagination may break #}
{% if DEFAULT_PAGINATION and (articles_page.has_previous() or articles_page.has_next()) %}
<div class="clear"></div>
<div class="pages">
{% if PAGINATION_PATTERNS[0][0] != 0 %}
{%- if articles_page.has_previous() %}
{% if articles_page.previous_page_number() == 1 %}
<a href="{{ SITEURL }}/" class="prev_page">&larr;&nbsp;Previous</a>
{%- else %}
<a href="{{ SITEURL }}/page/{{ articles_page.previous_page_number() }}" class="prev_page">&larr;&nbsp;Previous</a>
{%- endif %}
{%- endif %}
{%- if articles_page.has_next() %}
<a href="{{ SITEURL }}/page/{{ articles_page.next_page_number() }}" class="next_page">Next&nbsp;&rarr;</a>
{%- endif %}
{% else %}
{%- if articles_page.has_previous() %}
{% if articles_page.previous_page_number() == 1 %}
<a href="{{ SITEURL }}/{{ page_name }}.html" class="prev_page">&larr;&nbsp;Previous</a>
{%- else %}
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.previous_page_number() }}.html" class="prev_page">&larr;&nbsp;Previous</a>
{%- endif %}
{%- endif %}
{%- if articles_page.has_next() %}
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.next_page_number() }}.html" class="next_page">Next&nbsp;&rarr;</a>
{%- endif %}
{% endif %}
<span>Page {{ articles_page.number }} of {{ articles_paginator.num_pages }}</span>
</div>
{% endif %}

View file

@ -0,0 +1,5 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME }} | articles tagged "{{ tag }}"{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %}
{% block ephemeral_nav %}
{{ ephemeral_nav_link(tag, output_file, True) }}
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<ul>
{% for tag, articles in tags %}
<li><a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% if article.translations %}
Translations:
{% for translation in article.translations %}
<a href="{{ SITEURL }}/{{ translation.url }}">{{ translation.lang }}</a>
{% endfor %}
{% endif %}