Compare commits
11 commits
928e3afebd
...
7ae5406548
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ae5406548 | ||
|
|
e4f4cc6828 | ||
|
|
da6e536bb8 | ||
|
|
f39d6ac1d4 | ||
|
|
3165d050a1 | ||
|
|
458352da97 | ||
|
|
a1181a3eb2 | ||
|
|
7456c24200 | ||
|
|
09a1ba3ee4 | ||
|
|
03e447472f | ||
|
|
484a540327 |
12 changed files with 287 additions and 29 deletions
BIN
content/favicon.ico
Normal file
BIN
content/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
69
content/images/NO-AI.svg
Normal file
69
content/images/NO-AI.svg
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" viewBox="0 0 240 84">
|
||||
<title>NO AI</title>
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title>NO AI</dc:title>
|
||||
<dc:date>2025-06-26</dc:date>
|
||||
<dc:modified>2025-06-26</dc:modified>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Giuseppe Bilotta</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>noAI</rdf:li>
|
||||
<rdf:li>AI</rdf:li>
|
||||
<rdf:li>prohibition</rdf:li>
|
||||
<rdf:li>notByAI</rdf:li>
|
||||
<rdf:li>notForAI</rdf:li>
|
||||
<rdf:li>logo</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
||||
</cc:Work>
|
||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<style type="text/css"><![CDATA[
|
||||
#border { stroke: black; fill: #aab2ab ; stroke-width: 2}
|
||||
.aitext { stroke: black; fill: none ; stroke-width: 6}
|
||||
.nosign { stroke-width: 6 ; stroke: red ; fill: none}
|
||||
.background { stroke: none; fill: white}
|
||||
#labelholder { stroke: black; stroke-width: 2}
|
||||
.human { stroke-width: 4; stroke: black; fill: none}
|
||||
#human * { stroke: black; stroke-width: 1; fill: black}
|
||||
.noaitext { stroke: white; fill: none; stroke-width: 4}
|
||||
]]></style>
|
||||
<path id="border" d="M 120 1 h 115 a 4,4 0 0,1 4,4 v 78 h -238 v -78 a 4,4 0 0,1 4,-4 z"/>
|
||||
<g class="noai" transform="translate(41, 39)">
|
||||
<circle class="background" cx="0" cy="0" r="28"/>
|
||||
<path class="aitext" transform="translate(-10,11)" d="M1,0 v-12 q 0,-8 5,-8 q 5,0 5,8 v12 m 0,-10 h-10 M19,0 v -22"/>
|
||||
<g class="nosign">
|
||||
<circle cx="0" cy="0" r="29"/>
|
||||
<line x1="-20" y1="-20" x2="20" y2="20"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="human" transform="translate(153, 30)">
|
||||
<circle class="background" cx="0" cy="0" r="20"/>
|
||||
<g id="human" transform="translate(0,0)">
|
||||
<circle cx="0" cy="-12" r="3"/>
|
||||
<path d="M-3,14 v-11h-3v-9 a 1,1 0 0,1 1,-1 h10 a 1,1 0 0,1 1,1 v9h-3v11z"/>
|
||||
'/>
|
||||
</g>
|
||||
<circle class="human" cx="0" cy="0" r="21"/>
|
||||
</g>
|
||||
<path id="labelholder" d="M 1,60 h9 a 36,36 0 0,0 63,0 h166v22h-238z"/>
|
||||
<path class="noaitext" transform="translate(153,78)scale(0.8)" d="M-31,0 v-16 h4 l2,14 h4 v-16 m6,9 q 0,-7 5,-7 q 5,0 5,7 q 0,7 -5,7 q -5,0 -5,-7 M6,0 v-8 q 0,-8 5,-8 q 5,0 5,8 v8 m0,-8 h-8 m14,8 v -18"/>
|
||||
<script xmlns=""/></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -8,6 +8,10 @@ JsonLD: <script type="application/ld+json"> { "@context": "https://schema.org",
|
|||
|
||||
La Craft Letter est récente, et je me pose encore des questions sur son format. Dois-je continuer à faire des résumés détaillés, comme ceux que contient ce numéro à propos de Zig vs C3, ou de la modernisation de code legacy ? Ou des textes d’introduction plus courts sont-ils préférables, comme celui sur AtomVM ? Je vous serais reconnaissant si vous preniez quelques secondes pour répondre à ce [sondage minimaliste](https://framaforms.org/sondage-craft-letter-1771415205)🇫🇷 (il n’y a qu’une question). Les réponses m’aideront à définir le format des prochaines Craft Letters.
|
||||
|
||||
Par ailleurs, s’il vous arrive, comme moi, de vouloir retrouver un article que vous avez lu dans la Craft Letter, cela va s’avérer plus simple dorénavant. J’ai ajouté un moteur de recherche au site [craftletter.fr](https://www.craftletter.fr/), sur lequel vous pouvez retrouver toutes les newsletters que j’ai publiées. Un moteur local, très rapide, grâce à [stork](https://stork-search.net/).
|
||||
|
||||
Bonne lecture !
|
||||
|
||||
## Limiter les contributions open-source avec Vouch
|
||||
|
||||
Mitchell Hashimoto est un développeur prolifique et talentueux. Fondateur de HashiCorp (la société derrière Terraform, Vagrant, Vault...), il est aussi le créateur de Ghostty, un émulateur de terminal que je vous recommande. Son nouveau projet, [Vouch](https://github.com/mitchellh/vouch)🇬🇧, est un outil pour la CI qui limite les contributions aux projets open source (Pull Requests et Issues) à certains comptes de confiance. Les contributeurs ne peuvent pas en certifier d’autres : seuls les mainteneurs le peuvent, ce qui garantit qu’ils conservent la main sur leur projet. Par contre, ils peuvent échanger leurs listes de contributeurs de confiance.
|
||||
|
|
@ -15,7 +19,7 @@ Sans surprise, le but est de limiter les contributions de basse qualité génér
|
|||
|
||||
## Sérialisation rapide en Python
|
||||
|
||||
[MsgSpec](https://jcristharif.com/msgspec/)🇬🇧 est une librarie pour (dé)sérialisaliser et valider des données en Python, qu’elles soient au format JSON, YAML, TOML ou MessagePack. Elle utilise du C pour obtenir des performances élevées.
|
||||
[MsgSpec](https://jcristharif.com/msgspec/)🇬🇧 est une librarie pour (dé)sérialisaliser et valider des données en Python, qu’elles soient au format JSON, YAML, TOML ou MessagePack. Elle utilise du C pour obtenir des performances élevées. Si les besoin de validation ne sont pas poussés, c'est une alternative à Pydantic qui peut s'avérer nettement plus performante, tant en rapidité d'exécution qu'en consommation mémoire. Comme d'habitude, c'est à benchmarker dans votre cas d'usage précis.
|
||||
|
||||
## Qui, de Zig ou de C3, résout les problèmes du C ?
|
||||
|
||||
|
|
|
|||
123
content/newsletter/craft-letter-13.md
Normal file
123
content/newsletter/craft-letter-13.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
Title: Lettre n°13 — 02 mars 2026
|
||||
Date: 2026-03-02 09:00
|
||||
Category: Newsletter
|
||||
JsonLD: <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BlogPosting", "name": "Lettre n°13", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Mar 02 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } } </script>
|
||||
<img class="logo" alt="Logo Craft Letter" src="{static}/images/craftletter.svg">
|
||||
|
||||
# Lettre # 13
|
||||
|
||||
## Electrobun
|
||||
|
||||
Vous connaissez probablement [Electron](https://www.electronjs.org/)🇬🇧, un framework pour créer des applications Mac, Windows ou Linux à l’aide de technologies Web : HTML, JS et CSS. Il est utilisé par exemple pour le client Slack, mais aussi VSCode. Il combine une version de Chromium avec une instance de NodeJS. Il est régulièrement reproché à Electron la lourdeur des applications générées et leur consommation de ressources.
|
||||
|
||||
Il existe des équivalents basés sur d’autres technologies, qui génèrent des applications moins gourmandes. [Wails](https://wails.io/fr/)🇫🇷 repose sur Go ; comme pour tous les outils de ce type, l’IHM se code en JavaScript avec le framework de votre choix. [Tauri](https://tauri.app/fr/)🇫🇷 est basé sur Rust —mais propose une API qui permet de ne faire que du JavaScript ou TypeScript si vous ne voulez pas écrire de Rust. [Electrobun](https://blackboard.sh/electrobun/docs/)🇬🇧 est une nouvelle alternative, basée sur Zig et Typescript.
|
||||
|
||||
Mon expérience avec Wails s’est avérée meilleure que celle avec Tauri ; j’ai préféré la documentation du premier, et, surtout, la cross-compilation est triviale avec Wails, grâce à l’usage de Go.
|
||||
|
||||
## 10 APIs qui peuvent remplacer des librairies
|
||||
|
||||
Sylwia Laskowska liste [une série d’APIs](https://dev.to/sylwia-lask/stop-installing-libraries-10-browser-apis-that-already-solve-your-problems-35bi) disponibles dans certains navigateurs, et qui peuvent remplacer des bibliothèques.
|
||||
|
||||
Dans le même ordre d’idée, une nouvelle API vient d’arriver dans les principaux navigateurs : [Invoker command](https://www.infoq.com/news/2026/01/html-invoker-commands/), qui permet de contrôler l’ouverture et la fermeture de fenêtres contextuelles (popover, modal…), sans JavaScript.
|
||||
|
||||
## Utiliser des services européens pour monter une startup
|
||||
Robert délivre un [retour d’expérience](https://www.coinerella.com/made-in-eu-it-was-harder-than-i-thought/)🇫🇷 sur sa tentative de se passer des services états-uniens lors de la création d’une startup. Il décrit ce qui a été simple, ce qui l’a été moins, mais aussi ce qui n’a pas été possible.
|
||||
|
||||
Le site [European alternatives](https://european-alternatives.eu/)🇬🇧 référence des services qui pourraient vous servir si vous souhaitez vous lancer dans la même aventure.
|
||||
Attention toutefois, le tarif des services proposés par l’Allemand Hetzner [augmenteront de 30 à 40% au 1ᵉʳ Avril](https://docs.hetzner.com/general/infrastructure-and-availability/price-adjustment/)🇬🇧 😬
|
||||
|
||||
Les fondateurs de startups sont souvent attirés par les offres des GAFAM qui leur sont dédiées. Mais il existe des offres similaires chez les fournisseurs de cloud Français, comme chez [OVH](https://startup.ovhcloud.com/fr/), [Scaleway](https://www.scaleway.com/en/startup-program/), [Clever Cloud](https://www.selego.co/fr/perks/clevercloud-startup-plan) ou [Outscale](https://fr.outscale.com/outscale-for-entrepreneurs/), et sans doute d’autres auquels je n’ai pas pensé.
|
||||
|
||||
_NB : je n’ai aucune affiliation avec ces entreprises._
|
||||
|
||||
## Modern.css
|
||||
|
||||
[Modern.css](https://modern-css.com/)🇬🇧 montre comment des directives CSS disponibles dans les navigateurs récents peuvent remplacer des propriétés CSS anciennes plus verbeuses, voire du JavaScript. L’un des 75 exemples de code concerne des menus déroulants sans JavaScript. Pour chaque astuce, est indiqué le pourcentage d’utilisateurs potentiels dont le navigateur supporte ces nouvelles façons de faire. Les exemples proposés sont filtrables en fonction du support de la technique.
|
||||
|
||||
## CSS sémantique
|
||||
|
||||
Le principe des CSS sémantiques consiste à séparer totalement la présentation de la structure de la page HTML, et à minimiser les classes CSS requises.
|
||||
|
||||
Le site [CSS Zen Garden](https://csszengarden.com/)🇬🇧 est une démonstration impressionnante de ce qu’il est possible de faire en suivant ce principe. L’aspect du site change totalement en fonction du thème sélectionné, alors que le HTML est inchangé.
|
||||
|
||||
Sylvia Moreno explique [comment atteindre cet objectif](https://blog.octo.com/octo-le-css-semantique-ou-l’art-d’ecrire-du-style-qui-a-du-sens-1)🇫🇷, tout en améliorant l’accessibilité. Cela a aussi pour effet de simplifier le code HTML et d’améliorer la maintenabilité.
|
||||
|
||||
|
||||
## Oat : un framework UI sémantique
|
||||
|
||||
Voici un framework UI comme je les aime : simple, léger, qui utilise correctement les possibilités offertes par les CSS (petit tacle à Tailwind au passage, qui pollue le HTML avec de multiples classes CSS).
|
||||
|
||||
[Oat](https://oat.ink/)🇬🇧 pèse seulement 8 Ko, est accessible, et personnalisable à l’aide de variables CSS —aucun outillage n’est nécessaire. Il se base sur le principe des CSS sémantiques. Par exemple, une balise `<button>` sans attribut suffit pour déclarer un bouton dont le style est celui de l’action principale d’un écran ou d’une boîte de dialogue. Il est bien entendu possible de choisir des variantes pour le style des boutons, à l’aide d’attributs HTML ou de classes CSS, mais tout est fait pour réduire les déclarations requises au strict minimum.
|
||||
|
||||
Kailash Nadh, l’auteur de Oat, est aussi celui d’une [série de libraires JS minimales](https://oat.ink/other-libs/)🇬🇧.
|
||||
|
||||
## Animer des sprites en CSS
|
||||
|
||||
Josh Comeau explique comment reproduire, avec des propriétés CSS uniquement, une [animation à base de sprites](https://www.joshwcomeau.com/animation/sprites/)🇬🇧 telle qu’on en fait dans les jeux vidéo 2D.
|
||||
|
||||
## DataStar
|
||||
|
||||
Je vous ai [déjà parlé de Datastar](https://www.craftletter.fr/lettre-ndeg7-19-janvier-2026.html)🇫🇷, une libraire équivalente à HTMX, mais qui n’a pas les limitations de ce dernier concernant les applications complexes. Ces deux frameworks sont des alternatives aux Single Page Applications. Ils minimisent le code JavaScript, et envoient des blocs HTML depuis le serveur pour mettre à jour les pages.
|
||||
|
||||
Le modèle économique de DataStar repose sur une licence payante pour certaines fonctionnalités. Ce point a généré des débats enflammés, et sans intérêt selon moi : la licence est accordée à vie pour un paiement unique, et si on ne souhaite pas la payer il y a des alternatives —à commencer par utiliser un autre framework.
|
||||
|
||||
Ce qui est plus intéressant c’est que [les performances de DataStar sont excellentes](https://biggo.com/news/202510111317_Datastar-Pro-License-Debate)🇬🇧, ce qui n’est pas le cas de certaines technologies plus mainstream comme React.
|
||||
|
||||
## Moderniser un code legacy, partie III
|
||||
|
||||
Dans cette [troisième et dernière partie](https://blog.octo.com/apprivoiser-un-legacy-consequent-sans-y-perdre-les-plumes-partie-iii)🇫🇷, Bruno Boucart explique comment prioriser la refonte des composants identifiés précédemment.
|
||||
|
||||
La priorisation doit tenir compte :
|
||||
|
||||
* de la valeur métier de chacun des sous-domaines.
|
||||
* de la complexité du code existant, qui peut être mesurée par des outils ;
|
||||
* de la proximité fonctionnelle entre les services migrés (il suggère de migrer d’un coup un ensemble de services qui forment un ensemble cohérent) ;
|
||||
* de l’opportunité d’introduire des fonctionnalités à forte valeur ajoutée.
|
||||
|
||||
Les deux derniers points sont un moyen d’obtenir l’adhésion des utilisateurs à la modernisation du code.
|
||||
|
||||
La valeur métier de chacun des sous-domaines, ainsi que leur complexité, sont utilisés pour les classifier en trois catégories : coeur, support ou générique.
|
||||
|
||||
*Rappel :*
|
||||
|
||||
* *un sous-domaine **coeur de métier** est ce qui vous distingue de vos concurrents : pour une entreprise de paiement, ça pourrait être le traitement des cartes bancaires ;*
|
||||
* *un sous-domaine **support** est indispensable à votre activité, est spécifique à votre entreprise, mais n’est pas dans votre coeur de métier : toujours pour un service de paiement, ce pourrait être la détection de la fraude ;*
|
||||
* *un sous-domaine **générique** est un service que l’on peut trouver sur étagère, comme l’envoi d’emails.*
|
||||
|
||||
Bruno préconise d’utiliser le Core Domain Chart pour établir cette classification. C’est un diagramme qui peut servir de support pour les discussions entre les experts métier et l’équipe technique.
|
||||
|
||||
Les paramètres pour déterminer l’ordre dans lequel sera réalisée la migration sont multiples, c’est pourquoi il peut être utile d’établir un tableau comparatif. Il listera les critères ci-dessus et servira à tracer les décisions.
|
||||
|
||||
D’autres critères peuvent entrer en jeu, comme les évolutions fonctionnelles prévues, ou des dépendances fortes à d’autres équipes.
|
||||
|
||||
Une fois la priorisation établie, elle peut être mise en oeuvre par vagues successives. Cela limite les risques, et permet aux équipes qui ne passent pas en premier de bénéficier des apprentissages des précédentes.
|
||||
|
||||
Chaque vague comporte une réorganisation des équipes, puis une montée en compétence des équipes techniques, avec l’aide d’un coach, avant la modernisation du code à proprement parler.
|
||||
C’est également le coach qui aidera les développeurs à s’approprier les connaissances métier acquises pendant les ateliers préparatoires (évoqués dans la première partie).
|
||||
|
||||
## SoCraTes Rennes
|
||||
|
||||
Un peu d’auto-promo : la non-conférence [SoCraTes Rennes](https://socrates-rennes.github.io/)🇫🇷 aura lieu le 31 mars.
|
||||
|
||||
Même si le programme exact sera défini sur place, par les participants, je peux vous dévoiler en avant-première quelques-uns des sujets qui devraient être proposés :
|
||||
|
||||
- un coding dojo de découverte de Go, Rust ou Zig ;
|
||||
- une discussion sur les forces et faiblesses de ces langages ;
|
||||
- un retour d’expérience sur un projet personnel ;
|
||||
- un débat sur le thème : peut-on faire toute sa carrière comme “simple” dev ? ;
|
||||
- un autre sur : est-ce qu’il y a encore une place pour les développeur·se·s artisan·e·s non augmenté·e·s par l’IA ? ;
|
||||
- une présentation sur les outils et méthodes utilisés pour la réorganisation d’une application complexe avec une dette technique importante ;
|
||||
- un retour d’expérience sur des tests d’applications répartis ;
|
||||
- un coding dojo sur le Type-Driven Development ;
|
||||
- un échange à propos de l’impact du remote sur le travail d’équipe.
|
||||
|
||||
|
||||
Si vous n’avez pas encore [pris votre place](https://www.helloasso.com/associations/socratesfr/evenements/socrates-rennes-2026)🇫🇷, il est temps d’y penser ! Non seulement vous participerez à une journée d’échanges enrichissants avec des pairs, mais ce sera l’occasion de se rencontrer 😀
|
||||
|
||||
## Lyon Craft
|
||||
|
||||
[Lyon Craft](https://lyon-craft.fr)🇫🇷 sera de retour les 12 et 13 mai prochain, avec un nouveau format, sur deux jours au lieu d’un. La journée de conférences sera en effet suivie d’une non-conférence. La billetterie est ouverte. Vous pouvez proposer des sujets de conférence ou d’atelier en contactant Colin Damon [sur Linkedin](https://www.linkedin.com/in/colin-damon/) ou par e-mail à damon.colin chez Gmail.
|
||||
|
||||
---
|
||||
|
||||
Voilà, c’est tout pour aujourd’hui !
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
Title: Accueil
|
||||
Date: 2026-02-23 09:00
|
||||
Date: 2026-03-02 09:00
|
||||
URL:
|
||||
save_as: index.html
|
||||
Category: Home
|
||||
JsonLD: { "@context": "https://schema.org", "@type": "WebPage", "name": "Accueil", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Feb 23 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } }
|
||||
JsonLD: { "@context": "https://schema.org", "@type": "WebPage", "name": "Accueil", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Mar 02 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } }
|
||||
|
||||
<img class="logo" alt="Logo Craft Letter" src="{static}/images/craftletter.svg">
|
||||
|
||||
|
||||
# La [lettre n°12]({filename}/newsletter/craft-letter-12.md) est parue !
|
||||
# La [lettre n°13]({filename}/newsletter/craft-letter-13.md) est parue !
|
||||
|
||||
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...
|
||||
issus 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).
|
||||
|
||||
|
|
@ -37,6 +37,7 @@ Pour savoir qui je suis, ou pourquoi j'écris cette lettre, je vous invite à vo
|
|||
|
||||
# Archives
|
||||
|
||||
* [Lettre n°13]({filename}/newsletter/craft-letter-13.md)
|
||||
* [Lettre n°12]({filename}/newsletter/craft-letter-12.md)
|
||||
* [Lettre n°11]({filename}/newsletter/craft-letter-11.md)
|
||||
* [Lettre n°10]({filename}/newsletter/craft-letter-10.md)
|
||||
|
|
|
|||
13
justfile
13
justfile
|
|
@ -40,7 +40,8 @@ devserver-global:
|
|||
pelican -lr content -o output -s "{{CONFFILE}}" {{PELICANOPTS}} -b 0.0.0.0
|
||||
|
||||
# generate using production settings
|
||||
publish:
|
||||
publish number:
|
||||
just format {{number}}
|
||||
pelican content -o output -s "{{PUBLISHCONF}}" {{PELICANOPTS}}
|
||||
rsync -e ssh -av --delete-after /Users/pascal/Documents/craft-letter/output/ craftletter@ssh-craftletter.alwaysdata.net:/home/craftletter/www
|
||||
|
||||
|
|
@ -53,7 +54,11 @@ new number:
|
|||
PYTHONPATH=PWD venv/bin/python ./scripts/create_newsletter.py --number={{number}}
|
||||
|
||||
# generate HTML email
|
||||
mail file:
|
||||
echo {{file}}
|
||||
PYTHONPATH=PWD venv/bin/python ./scripts/prepare_email.py {{file}}
|
||||
mail number:
|
||||
just format {{number}}
|
||||
PYTHONPATH=PWD venv/bin/python ./scripts/prepare_email.py --number={{number}}
|
||||
|
||||
# Format the content of a given newsletter
|
||||
format number:
|
||||
PYTHONPATH=PWD venv/bin/python ./scripts/format.py --number={{number}}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
<td style="padding: 0; vertical-align: middle;">
|
||||
<div style="{{SIGNATURE_STYLE}}">
|
||||
{{SIGNATURE_TEXT}}
|
||||
Retrouvez tous les numéros de la Craft Letter sur <a href="https://www.craftletter.fr">craftletter.fr</a>.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -69,7 +70,7 @@
|
|||
<tbody style="font-size: 13px; line-height: 1.5; color: #666666; padding: 0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 40px;">
|
||||
<a href="https://www.craftletter.fr">Craft Letter</a> © 2025 par <a href="https://www.linkedin.com/in/pascal-le-merrer/">Pascal Le Merrer</a><br>est sous licence <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a></p><div><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="max-width:1em;max-height:1em;margin-left:.2em"><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="max-width:1em;max-height:1em;margin-left:.2em"><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" style="max-width:1em;max-height:1em;margin-left:.2em"><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/nd.svg" style="max-width:1em;max-height:1em;margin-left:.2em">
|
||||
<a href="https://www.craftletter.fr">Craft Letter</a> © 2025 - {{YEAR}} par <a href="https://www.linkedin.com/in/pascal-le-merrer/">Pascal Le Merrer</a><br>est sous licence <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a></p><div><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="max-width:1em;max-height:1em;margin-left:.2em"><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="max-width:1em;max-height:1em;margin-left:.2em"><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" style="max-width:1em;max-height:1em;margin-left:.2em"><img alt="" src="https://mirrors.creativecommons.org/presskit/icons/nd.svg" style="max-width:1em;max-height:1em;margin-left:.2em">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
AUTHOR = 'Pascal Le Merrer'
|
||||
SITENAME = 'Craft Letter'
|
||||
SITEURL = "" # https://www.craftletter.fr"
|
||||
AUTHOR = "Pascal Le Merrer"
|
||||
SITENAME = "Craft Letter"
|
||||
SITEURL = "" # https://www.craftletter.fr"
|
||||
SITEDESCRIPTION = "Newsletter hebdomadaire de veille technologique, consacrée au développement logiciel."
|
||||
|
||||
PATH = "content"
|
||||
|
||||
TIMEZONE = 'Europe/Paris'
|
||||
TIMEZONE = "Europe/Paris"
|
||||
|
||||
DEFAULT_LANG = 'fr'
|
||||
DEFAULT_LANG = "fr"
|
||||
|
||||
# Feed generation is usually not desired when developing
|
||||
FEED_ALL_RSS = "feeds/all.rss.xml"
|
||||
|
|
@ -18,9 +18,7 @@ AUTHOR_FEED_ATOM = None
|
|||
AUTHOR_FEED_RSS = None
|
||||
|
||||
# Blogroll
|
||||
LINKS = (
|
||||
("S'abonner à la lettre de veille", "https://www.craftletter.fr"),
|
||||
)
|
||||
LINKS = (("S'abonner à la lettre de veille", "https://www.craftletter.fr"),)
|
||||
|
||||
# Social widget
|
||||
SOCIAL = (
|
||||
|
|
@ -36,8 +34,7 @@ DEFAULT_PAGINATION = 10
|
|||
THEME = "themes/blue-penguin"
|
||||
|
||||
JINJA_ENVIRONMENT = {
|
||||
'extensions': ['jinja2.ext.i18n'],
|
||||
"extensions": ["jinja2.ext.i18n"],
|
||||
}
|
||||
|
||||
STATIC_PATHS = ['images', 'robots.txt']
|
||||
|
||||
STATIC_PATHS = ["images", "robots.txt", "favicon.ico"]
|
||||
|
|
|
|||
22
scripts/format.py
Normal file
22
scripts/format.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
REPLACEMENTS = {
|
||||
" :": " :",
|
||||
" ;": " ;",
|
||||
" !": " !:",
|
||||
" ?": " ?",
|
||||
"'": "’",
|
||||
}
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-n", "--number", required=True, type=int, help="Newsletter number")
|
||||
args = parser.parse_args()
|
||||
|
||||
file = Path(f"./content/newsletter/craft-letter-{args.number}.md")
|
||||
|
||||
content = file.read_text()
|
||||
for value, replacement in REPLACEMENTS.items():
|
||||
content = content.replace(value, replacement)
|
||||
|
||||
file.write_text(content)
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#!python
|
||||
# Copies the given input file, removing the parts specific to the Web,
|
||||
# so it can be converted to an email
|
||||
from datetime import date
|
||||
from docutils.parsers.rst.directives.misc import Date
|
||||
import subprocess
|
||||
|
||||
import argparse
|
||||
|
|
@ -11,13 +13,10 @@ MAIL_GENERATOR = "/opt/homebrew/bin/mdtosendy"
|
|||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source")
|
||||
parser.add_argument("-n", "--number", required=True, type=int, help="Newsletter number")
|
||||
args = parser.parse_args()
|
||||
|
||||
root = Path.cwd()
|
||||
print(f"Root: {root}")
|
||||
|
||||
source = Path(root / args.source)
|
||||
source = Path(f"./content/newsletter/craft-letter-{args.number}.md")
|
||||
|
||||
if not source.is_file():
|
||||
print(f"ERROR: file not found {source}")
|
||||
|
|
@ -34,4 +33,12 @@ destination = Path.cwd() / "mail" / source.name
|
|||
print(f"Writing {destination}")
|
||||
destination.write_text(output)
|
||||
|
||||
subprocess.run([MAIL_GENERATOR, "--preview", str(destination)])
|
||||
subprocess.run([MAIL_GENERATOR, str(destination)])
|
||||
|
||||
generated_mail = Path("mail") / f"craft-letter-{args.number}.html"
|
||||
today = date.today()
|
||||
mail_content = generated_mail.read_text()
|
||||
mail_content = mail_content.replace("{{YEAR}}", str(today.year))
|
||||
generated_mail.write_text(mail_content)
|
||||
|
||||
subprocess.run(["open", str(generated_mail)])
|
||||
|
|
|
|||
|
|
@ -358,6 +358,7 @@ nav {
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
|
|
@ -365,6 +366,10 @@ nav ul {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
nav ul:nth-child(2) {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
|
@ -493,3 +498,10 @@ hr {
|
|||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.stork-close-button {
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
#noai {
|
||||
width: 100px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
{% 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="https://files.stork-search.net/basic.css" />
|
||||
<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" />
|
||||
|
|
@ -51,7 +52,6 @@
|
|||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:description" content="{{ SITEDESCRIPTION }}" />
|
||||
|
||||
<link rel="stylesheet" href="https://files.stork-search.net/basic.css" />
|
||||
{% endblock head %}
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -96,9 +96,25 @@
|
|||
<main>
|
||||
{%- block content -%}{%- endblock %}
|
||||
<!-- </main> -->
|
||||
|
||||
{% if DISPLAY_FOOTER or DISPLAY_FOOTER is not defined %}
|
||||
<div class="clear"></div>
|
||||
<footer>
|
||||
<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>
|
||||
<p>
|
||||
Thème dérivé de <a href="https://github.com/jody-frankowski/blue-penguin">Blue Penguin</a>
|
||||
·
|
||||
|
|
@ -110,6 +126,7 @@
|
|||
<p>
|
||||
<a href="https://www.craftletter.fr">Craft Letter</a> © 2025 - 2026 par <a href="https://www.linkedin.com/in/pascal-le-merrer/">Pascal Le Merrer</a> est sous licence <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a><div><img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="CC" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="BY" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" alt="NC" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/nd.svg" alt="ND" style="max-width: 1em;max-height:1em;margin-left: .2em;"></div>
|
||||
<p/>
|
||||
<img id="noai" src="images/NO-AI.svg"/>
|
||||
</footer>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue