<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jujens’ blog - Blog</title><link href="https://www.jujens.eu/" rel="alternate"></link><link href="https://www.jujens.eu/feeds/blog.atom.xml" rel="self"></link><id>https://www.jujens.eu/</id><updated>2025-12-20T00:00:00+01:00</updated><entry><title>Jouer sous Linux</title><link href="https://www.jujens.eu/posts/2025/Dec/20/jouer-sous-linux/" rel="alternate"></link><published>2025-12-20T00:00:00+01:00</published><updated>2025-12-20T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-12-20:/posts/2025/Dec/20/jouer-sous-linux/</id><summary type="html">&lt;p&gt;Après plusieurs tests infructueux l’année dernière, j’ai décidé de retester le jeu sous Linux en avril 2025 avec une nouvelle config.
Et je dois admettre que ça fonctionne vraiment très bien.
Je n’ai presque pas de problèmes et un seul jeu aurait nécessité de repasser sous Windows …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Après plusieurs tests infructueux l’année dernière, j’ai décidé de retester le jeu sous Linux en avril 2025 avec une nouvelle config.
Et je dois admettre que ça fonctionne vraiment très bien.
Je n’ai presque pas de problèmes et un seul jeu aurait nécessité de repasser sous Windows le temps qu’un correctif le corrige.
Il y a toutefois quelques points sur lesquels j’ai un peu coincé et qui méritent qu’on les détaille.
Ce que je vais faire dans cet article.&lt;/p&gt;
&lt;p&gt;J’ai essayé de faire des sections claires pour vous permettre de ne lire que ce qui vous intéresse.&lt;/p&gt;
&lt;p&gt;TL;DR : ça fonctionne très bien et je ne joue plus du tout sous Windows.
On peut quand même avoir des petits soucis qui finissent par être corrigés.
Si vous voulez être sûr de pouvoir jouer à n’importe quel jeu dès sa sortie, mieux vaut garder un Windows dans un coin au cas où.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#contexte" id="toc-entry-1"&gt;Contexte&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#vers-le-jeu-sous-linux" id="toc-entry-2"&gt;Vers le jeu sous Linux&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#avec-steam" id="toc-entry-3"&gt;Avec Steam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#et-gog" id="toc-entry-4"&gt;Et GoG ?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#les-jeux" id="toc-entry-5"&gt;Les jeux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="contexte"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Contexte&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tout d’abord, un peu de contexte.
J’ai essayé l’année dernière vers décembre après que des collègues m’aient dit jouer sous Linux sans problème.
Malheureusement, sur mon ancienne config avec une vieille Nvidia GTX 1070, je n’ai jamais réussi à lancer les jeux Steam à cause d’une erreur sur la carte graphique.
Mes recherches n’ont rien donné et après plusieurs tests, j’ai lâché l’affaire et je suis retourné sous Windows.&lt;/p&gt;
&lt;p&gt;Je suppose que le souci n’était pas matériel ni au niveau de l’OS car mes jeux GOG fonctionnaient sans souci apparent (je n’ai pas poussé mes tests très loin).&lt;/p&gt;
&lt;p&gt;En début d’année 2025, comme plusieurs jeux qui m’intéressaient allaient sortir et que ma machine commençait à vieillir, j’ai décidé de refaire ma config.
C’est surtout la carte graphique qui posait problème : j’avais acheté un écran 1440p fin d’année dernière et la GTX 1070 montrait ses limites sur les jeux récents.
En 1080p, je trouve qu’elle fonctionne toujours très bien et les jeux, même récents, restaient beaux.
Au delà des problèmes de performances, les mises à jour du noyau avaient tendance à rater sous openSUSE Tumbleweed (ma distro) car les pilotes ne sont pas mis à jour rapidement dans le dépôt Nvidia.&lt;/p&gt;
&lt;p&gt;J’ai décidé de partir sur une carte AMD RX9070XT, en grande partie à cause de ces soucis de support des cartes Nvidia sous Linux.
De ce que j’ai compris, sur les modèles plus récents, le pilote officiel est mieux intégré au noyau et on évite toutes ces galères.
Je pense quand même qu’AMD est un meilleur choix, ne serait-ce que pour leur meilleur support de Linux depuis de nombreuses années.
Tout test simple : rien à installer, tout vient de base avec le noyau.&lt;/p&gt;
&lt;p&gt;Je signale quand même un souci que j’ai eu avec la carte : le son pouvait produire des genres de craquements via Display Port à la lecture de musique (je n’ai jamais eu de souci en jeu bizarrement).
Une mise à jour de noyau vers une nouvelle version a résolu le souci.
Comme la carte est un modèle récent, je pardonne.
Cela reste un point à garder en tête : si vous prenez du matériel récent, mieux vaut une rolling release ou une distro comme Fedora bien à jour.&lt;/p&gt;
&lt;p&gt;Une fois la config montée, j’ai commencé par vérifier que tout semblait bon sous Windows en jouant un peu.
J’ai vite eu l’impression que le système n’était pas très stable avec plusieurs jeux qui ont planté et bien plus qu’avec ma vieille GTX 1070.
Surement, car je n’ai pas réinstallé Windows en changeant de carte.
J’ai simplement désinstallé les pilotes Nvidia et installé ceux d’AMD.
Je suppose qu’une réinstallation propre de Windows règlerait le souci, mais, comme j’arrive à jouer sous Linux sans souci, eh bien, je ne vais pas me prendre la tête avec ça.
Windows va rester installé au cas où, ne serait-ce que pour déboguer mon imprimante.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="vers-le-jeu-sous-linux"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Vers le jeu sous Linux&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="avec-steam"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Avec Steam&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Comme dans mes tests de fin 2024, j’ai eu des soucis avec Steam, quand bien même c’est ce que tous les joueurs sous Linux que je connais me vendaient comme la panacée.
J’ai décidé de passer par le Flatpak pour que les jeux n’aient pas accès aux documents système par sécurité.
Cela vient peut-être du fait que je me souviens d’un gros bogue de Steam qui supprimait tous les fichiers de &lt;tt class="docutils literal"&gt;$HOME&lt;/tt&gt; il y a quelques années.&lt;/p&gt;
&lt;p&gt;Par défaut, seuls les jeux natifs Linux peuvent être installés et lancés.
Pour lancer n’importe quels jeux, il faut activer Steam Play pour tous les jeux dans les paramètres section &amp;quot;Compatibilité&amp;quot;.
Une fois cela fait, je télécharge un jeu et j’essaie de le lancer.&lt;/p&gt;
&lt;p&gt;Et c’est le drame.
Rien ne se passe.
Même pas un message d’erreur.&lt;/p&gt;
&lt;p&gt;J’essaie avec un autre jeu, et même problème.
J’essaie différentes versions de Proton (la couche de compatibilité de Valve pour jouer aux jeux Windows sous Linux qu’on peut configurer en cas de souci).
Sans succès.&lt;/p&gt;
&lt;p&gt;Je me suis dit qu’il me manquait peut-être certains paquets.
J’ai essayé d’en installer plusieurs en lien avec Mesa ou AMDGPU.
Sans succès.&lt;/p&gt;
&lt;p&gt;J’ai essayé un jeu GOG via &lt;a class="reference external" href="https://heroicgameslauncher.com"&gt;Heroic Game Launcher&lt;/a&gt; qui a fonctionné sans problème avec Wine.&lt;/p&gt;
&lt;p&gt;Finalement, j’ai essayé d’installer le paquet Steam fourni par les dépôts.
Le gestionnaire de paquets a ajouté plusieurs autres paquets et, miracles, je pouvais jouer.
J’ai réessayé la version Flatpak qui fonctionne aussi correctement une fois ces paquets installés.
J’ai décidé de rester sur la version Flatpak avec le paquet installé et pour séparer mes activités et être sûr qu’aucun jeu n’aura accès à mes fichiers, j’ai aussi créé un utilisateur dédié pour le jeu.
Mais c’est peut-être juste moi qui suis un peu parano.&lt;/p&gt;
&lt;p&gt;Après plusieurs mois de jeux sous Linux, je ne me vois plus lancer un Windows.
Ça fonctionne trop bien !
Je suis même surpris d’à quel point ça fonctionne bien, y compris sur des jeux comme &lt;em&gt;Avowed&lt;/em&gt;, &lt;em&gt;The Outer Worlds 2&lt;/em&gt;, &lt;em&gt;Assassin’s Creed Shadows&lt;/em&gt; qui sont sortis cette année.&lt;/p&gt;
&lt;p&gt;J’ai même réussi à jouer à &lt;em&gt;Mass Effect Legendary Edition&lt;/em&gt; et &lt;em&gt;Dragon Age Inquisition&lt;/em&gt; qui nécessite cette 🤬 d’EA app.
Notez que je passe par Steam pour lancer les jeux : Steam installe et lance l’EA app automatiquement et ça fonctionne aussi bien (ou mal) que sous Windows.
Il suffit de se connecter à l’EA app.
Malheureusement, après une mise à jour de l’EA app, &lt;em&gt;DA:I&lt;/em&gt; ne se lançait plus (idem pour &lt;em&gt;Mass Effect&lt;/em&gt;.
Supprimer manuellement un fichier de cache a réglé le souci.
Pour cela, il faut : trouver l’id Steam du jeu (visible dans les paramètres du jeu dans la section &amp;quot;Mise à jour&amp;quot;) puis supprimer le dossier &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/compatdata/&amp;lt;APP_ID&amp;gt;&lt;/span&gt;&lt;/tt&gt; (c’est le chemin avec Flatpak, ça devrait être quelque chose comme &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.local/share/Steam/steamapps/compatdata/&amp;lt;APP_ID&amp;gt;&lt;/span&gt;&lt;/tt&gt; pour la version standard).
Ça me confirme dans l’idée que l’EA est naze.&lt;/p&gt;
&lt;p&gt;Quelques petits soucis quand même :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Sur &lt;em&gt;Dragon Age the Veilguard&lt;/em&gt;, les coiffures de certains personnages se sont mises à scintiller après une mise à jour système.
Le jeu restait jouable, mais avait un aspect très bizarre.
Une autre mise à jour quelque temps après a réglé le souci.&lt;/li&gt;
&lt;li&gt;Avec &lt;em&gt;Indiana Jones and the Great Circle&lt;/em&gt;, j’ai dû attendre plusieurs mois avant que le jeu ne se lance correctement.
Les textures n’étaient pas rendues correctement et le jeu était injouable.
C’est le plus gros souci que j’ai eu et le seul jeu non jouable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="et-gog"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Et GoG ?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;En plus de Steam, j’ai aussi une belle bibliothèque sur &lt;a class="reference external" href="https://www.gog.com/fr/"&gt;GoG&lt;/a&gt;.
Même si elle n’a pas d’app compatible Linux, elle reste ma boutique de choix : les jeux n’ont pas de DRM et elle est basée en Europe.
Pour me simplifier la vie, j’ai choisi de passer par &lt;a class="reference external" href="https://heroicgameslauncher.com"&gt;Heroic Game Launcher&lt;/a&gt; installé via Flatpak (qui gère aussi les jeux Epic Games Store).
C’est surtout pour ça que j’aime Flatpak : être sûr d’avoir des logiciels pas très courants correctement packagé pour Linux.&lt;/p&gt;
&lt;p&gt;En soi, le fonctionnement est très proche de Steam : on peut installer les jeux et les lancer.
L’app se charge de les garder à jour et traque le temps de jeu.
Les succès ne sont pas visibles dans l’app, mais sont correctement débloqués (j’ai vérifié sous l’app GoG sous Windows).&lt;/p&gt;
&lt;p&gt;Petit point de configuration pour que les jeux natifs Linux se lancent : il faut configurer le lien vers les fichiers de compatibilité de Steam.
Sans, certains jeux ne peuvent pas se lancer, car ils nécessitent une vieille version d’openSSL.
Pour cela, il faut s’assurer que le paramètre &amp;quot;Chemin par défaut vers Steam&amp;quot; pointe bien vers &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.var/app/com.valvesoftware.Steam/.local/share/Steam&lt;/span&gt;&lt;/tt&gt; (je suppose que c’est &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.local/share/Steam&lt;/span&gt;&lt;/tt&gt; pour la version standard).
Ça devrait être le cas par défaut.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-jeux"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Les jeux&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pour terminer, voici une liste des jeux auxquels j’ai joué cette année sous Linux avec la date de sortie du jeu, l’application utilisée (Steam ou Heroic) et quelques commentaires.
Principalement mon temps de jeu ou si j’ai eu des soucis (et dans ce cas, lesquels).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Dragon Age: the Veilguard&lt;/em&gt;, 2024, Steam : presque complet (j’ai démarré sous Windows).
Le jeu est plus stable avec les paramètres élevés que sous Windows.
Quelques plantages à l’utilisation du pouvoir ultime ou lors de très gros combats.
Réduire le ray tracing a résolu le souci.
J’ai eu exactement le même problème au même endroit sous Windows avec mon ancienne config.&lt;/p&gt;
&lt;p&gt;Le curseur n’est pas le même que sous Windows et semble troué.
Étrange, mais pas impactant.&lt;/p&gt;
&lt;p&gt;Plus grave, j’ai eu un souci avec le rendu des cheveux après une mise à jour : ils scintillaient.
Le jeu restait jouable, mais était très moche.
Ça a mis plusieurs semaines à revenir à la normale avec une mise à jour système.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Pillars of Eternity&lt;/em&gt;, 2015, Heroic : jeu de base complet, sur mon portable.
J’ai utilisé la version Linux avec les compatibilités Steam pour éviter les soucis avec la vieille version d’openSSL dont le jeu a besoin.
Ça fonctionne très bien.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Avowed&lt;/em&gt;, Steam, 2025 : fonctionne bien en paramètres max, et ce, dès sa sortie.
Je n’ai pas eu un seul crash sur la totalité du jeu.
Seul petit souci rencontré : des gels graphiques de temps en temps, en général après un changement de zone.
Ces gels disparaissent après quelques secondes.
C’est plus agaçant qu’autre chose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Age of Empires IV&lt;/em&gt;, 2021, Steam : j’ai joué plusieurs dizaines d’heures en solo et en multi sans souci.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Age of Mythology Retold&lt;/em&gt;, 2024, Steam : j’ai joué plusieurs dizaines d’heures en solo et en multi sans souci.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Indiana Jones et le cercle ancien&lt;/em&gt;, 2024, Steam : j’ai joué plusieurs heures sans souci &lt;em&gt;après&lt;/em&gt; des débuts compliqués.
Quand j’ai essayé pour la première fois, les textures étaient complètement cassées.
Ça a fini par fonctionner après une mise à jour système.
J’ai des lenteurs quand la caméra tourne même en texture moyenne ou avec fsr et textures élevées ou moyennes.
C’est le seul jeu avec ce problème et celui qui fonctionne le moins bien.
Comme je n’y ai pas accroché, je n’ai pas cherché plus loin.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Mass Effect Legendary&lt;/em&gt;, 2021, Steam : jeu complet, aucun souci.
Après avoir eu des soucis à cause de l’EA app avec Dragon Age Inquisition (voir le jeu suivant), j’ai essayé de le relancer et j’ai eu le même comportement.
La même solution a corrigé le souci.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Dragon Age Inquisition&lt;/em&gt;, 2014, Steam : jeu complet, y compris les DLCs.
J’ai eu quelques bogues de quêtes qui se valident en boucle.
La quête finit par se valider correctement après plusieurs &amp;quot;essais&amp;quot;.
J’ai aussi eu un plantage avant le boss de fin.&lt;/p&gt;
&lt;p&gt;Plus grave : suite à une mise à jour de l’EA app, le jeu ne se lançait plus du tout.
J’ai dû vider le cache en supprimant un dossier à la main.
Voir le détail dans la section &lt;a class="reference internal" href="#avec-steam"&gt;avec Steam&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Baldur’s Gate 3&lt;/em&gt;, 2023, Heroic : quelques heures, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Dragon Age Origins&lt;/em&gt;, 2009, Heroic : j’ai seulement fait le prologue, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Witcher 3&lt;/em&gt;, 2015, Heroic : plusieurs heures de jeux, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;The Outer Worlds Spacer Choice edition&lt;/em&gt;, 2019, Heroic : jeu complet, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Pathfinder Wrath of the Righteous&lt;/em&gt;, 2021, Heroic : quelques heures, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Assassin’s creed Odyssey&lt;/em&gt;, 2018, Steam : quelques heures, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Assassin’s creed Shadows&lt;/em&gt;, 2025, Steam : jeu de base, joué dès sa sortie, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Horizon Zero Dawn&lt;/em&gt;, 2017, Steam : jeu complet, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;The Outer Worlds 2&lt;/em&gt;, 2025, Steam: jeu complet, joué dès sa sortie, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Horizon forbidden west&lt;/em&gt;, 2022, Steam : quelques heures, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;em&gt;Spellforce 3 versus&lt;/em&gt;, 2017, Steam : quelques heures, RAS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Blog"></category></entry><entry><title>À propos de mon blog</title><link href="https://www.jujens.eu/posts/2025/Sep/05/a-propos-blog/" rel="alternate"></link><published>2025-09-05T00:00:00+02:00</published><updated>2025-09-05T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-09-05:/posts/2025/Sep/05/a-propos-blog/</id><summary type="html">&lt;p&gt;Après plus de dix ans, voici mon 200e article de blog !
J’ai décidé que ce serait un bon moment pour faire un point sur ces années et envisager le futur.&lt;/p&gt;
&lt;p&gt;J’ai démarré ce blogue en 2013 alors que j’étais encore étudiant à Centrale Marseille.
Il était alors …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Après plus de dix ans, voici mon 200e article de blog !
J’ai décidé que ce serait un bon moment pour faire un point sur ces années et envisager le futur.&lt;/p&gt;
&lt;p&gt;J’ai démarré ce blogue en 2013 alors que j’étais encore étudiant à Centrale Marseille.
Il était alors fait en Drupal et hébergé sur les serveurs de l’école.&lt;/p&gt;
&lt;p&gt;J’ai assez rapidement décidé d’en faire un site statique pour avoir quelque chose de plus simple et de plus facilement hébergeable et de le migrer sur mon domaine pour être plus indépendant (ma remise de diplôme approchait).
Comme je faisais déjà pas mal de Python, j’ai choisi &lt;a class="reference external" href="https://getpelican.com"&gt;Pelican&lt;/a&gt; un générateur écrit en Python.
De mémoire, j’avais aussi regardé &lt;a class="reference external" href="https://jekyllrb.com"&gt;Jekyll&lt;/a&gt; une autre solution très populaire à l’époque écrite en Ruby.
Aujourd’hui, j’étudierai aussi &lt;a class="reference external" href="https://gohugo.io"&gt;Hugo&lt;/a&gt; une solution écrite en Go qui me semble de plus en plus populaire.&lt;/p&gt;
&lt;p&gt;Pour ce blogue, je compte rester sur Pelican : ça fonctionne très bien, c’est suffisant pour mes besoins et comme j’ai peu d’articles le site est généré en moins d’une seconde.
Je n’ai donc pas besoin des performances d’Hugo.
En somme, aucune bonne raison de changer.
Changer serait même difficile : mes articles sont écrits en &lt;a class="reference external" href="https://docutils.sourceforge.io/rst.html"&gt;reStructuredText&lt;/a&gt; et pas en Markdown (plus d’info à ce sujet ci-dessous) ce qui fonctionne avec Python moins dans les autres langages.&lt;/p&gt;
&lt;p&gt;Le blogue a assez peu changé : je continue à bloguer sur les sujets tech qui m’intéressent.
Certaines catégories n’ont pas eu d’articles récemment n’en auront surement jamais : elles correspondent à mes sujets d’intérêts quand j’ai lancé le blog.
Seule la programmation est vraiment restée.
L’expérience aidant, j’ai aussi l’impression d’avoir moins de sujets qui méritent un article.
De ce point de vue, 2025 s’annonce comme une année exceptionnelle avec beaucoup de sujets.&lt;/p&gt;
&lt;p&gt;Mon blogue reste assez peu connu.
C’est principalement ma faute, je fais peu de pub, car ça ne m’intéresse pas.
Il faut croire qu’écrire pour moi-même me suffit et que je préfère clarifier mes idées au fait d’être lu.
Au début, je postais les liens sur Twitter pour avoir un peu de visibilité.
Désormais, j’ai quitté Twitter où je n’étais de toute façon pas actif.
J’ai un compte Mastodon, plus pour suivre des choses que pour poster.
Et peu de personnes me suivent.&lt;/p&gt;
&lt;p&gt;J’ai toujours des statistiques anonymes et compatibles RGPD sans bannière de cookies.
Au début, j’autohébergeais une instance Matomo.
Pour me simplifier la vie et gagner du temps, &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Mar/09/from-matomo-to-plausible/"&gt;je suis passé sur plausible cette année&lt;/a&gt; : c’est simple, suffisant pour mon usage, RGPD compatible par défaut et pas très cher (9 € par mois, ce que je peux me permettre maintenant).
Mon article le plus vu d’année en année reste un article publié en 2013 sur &lt;a class="reference external" href="/posts/2013/Oct/20/latex-page-garde/"&gt;la création d’une page de garde en LaTeX&lt;/a&gt; avec le double de vues des autres articles.
Au coude à coude viennent ensuite un article sur &lt;a class="reference external" href="/posts/2015/Jan/09/utiliser-trap-bash/"&gt;l’utilisation de trap en Bash&lt;/a&gt; et sur un sur &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2017/Jul/02/docker-userns-remap/"&gt;les espaces de nom dans Docker&lt;/a&gt;.
Pour les articles publiés cette année, celui sur &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Jan/25/uv-and-ruff/"&gt;uv et ruff&lt;/a&gt; et celui sur &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/26/pydantic-enums/"&gt;Pydantic et les enums&lt;/a&gt; fonctionnent très bien.&lt;/p&gt;
&lt;p&gt;La langue d’écriture a aussi changé.
Initialement, j’écrivais tout en français, puis j’ai proposé des traductions de mes articles en anglais pour accroitre leur visibilité.
Comme la traduction demandait beaucoup de temps, maintenant j’écris en anglais, sauf pour de rares articles spéciaux comme celui-ci.&lt;/p&gt;
&lt;p&gt;L’hébergement a changé également.
Je suis passé d’un site Drupal, à un site statique hébergé sur mon VPS à un site statique dans des buckets (chez &lt;a class="reference external" href="https://www.scaleway.com/en/docs/object-storage/how-to/use-bucket-website/"&gt;Scaleway&lt;/a&gt; pour éviter les géants américains).
C’est encore plus simple (plus de serveur), et fonctionne très bien.
C’est aussi moins cher.
J’ai configuré la CSP via le HTML, car je n’ai pas l’impression qu’on puisse configurer ces entêtes dans les buckets.
Je trouve que c’est moins bon, mais ça fonctionne.
Je garde un VPS pour d’autres projets qui ont besoin d’un serveur, dont &lt;a class="reference external" href="https://github.com/isso-comments/isso/"&gt;mes commentaires qui restent autohébergés grace à isso&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Concernant le format des sources, je suis toujours sur reStructuredText.
Pelican fonctionne avec reSTtructuredText (via &lt;tt class="docutils literal"&gt;docutils&lt;/tt&gt; l’implémentation de référence pour ce format) et Markdown par défaut.
À titre personnel, je n’ai jamais accroché au Markdown : je le trouve pas assez spécifié et il a trop de variantes pas forcément compatibles entre elles.
Avec reStrucuturedText, je peux inclure des fichiers, avoir les numéros de lignes à côté du code…
J’ai donc plus de fonctionnalités qui fonctionnent correctement et sont correctement standardisées.
Cela me convient mieux, même si je dois reconnaitre que le format manque de flexibilité.
C’est, je pense, la force de Mardown : les cas simples sont gérés correctement par tout le monde et pas de prise de tête avec l’indentation, on écrit du texte un peu n’importe comment et ça fonctionne.
Je doute qu’un langage à balisage sans cette caractéristique puisse devenir populaire, même si parser les cas plus complexes ou avoir toutes les fonctionnalités dont vous avez besoin est plus difficile.&lt;/p&gt;
&lt;p&gt;Et l’IA dans tout ça ?
Je n’ai pas vraiment d’avis.
Comme la visibilité n’a que peu d’impact, je vais continuer à bloguer même si moins de personnes visitent mon blog.
Si ça devient vraiment un souci et que je ne veux pas nourrir les LLM, je pense que je continuerais à écrire, &lt;em&gt;pour moi&lt;/em&gt; sans rien publier.
Ce serait dommage, car je sais que certains de mes articles sont utiles.
Affaire à suivre…&lt;/p&gt;
&lt;p&gt;Pour finir : je suis très content de ce blogue.
Il me force à mettre mes idées au clair, me permet de partager certaines trouvailles, et, je pense, qu’il a fait de moi un meilleur développeur.
Je compte continuer à bloguer quand j’ai envie sur les sujets qui m’intéressent.
Je ne pense pas changer quoique ce soit sur la partie technique : ça fonctionne et j’en suis satisfait.
Le fait que le blogue soit statique est un gros avantage, je pense, et je ne peux que vous recommander de faire un site statique vous aussi, si vous voulez un blogue : tout est plus simple, on peut utiliser &lt;tt class="docutils literal"&gt;git&lt;/tt&gt; pour gérer les articles et il y a pléthore de bons outils pour vous simplifier la tâche.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Dans un bucket, on ne peut pas définir des entêtes HTTP personnalisés dont &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;X-Frame-Options&lt;/span&gt;&lt;/tt&gt; pour empêcher l’inclusion du site dans une iframe.
Utiliser &lt;tt class="docutils literal"&gt;&amp;lt;meta &lt;span class="pre"&gt;http-equiv=&amp;quot;X-Frame-Options&amp;quot;&lt;/span&gt; &lt;span class="pre"&gt;content=&amp;quot;DENY&amp;quot;&amp;gt;&lt;/span&gt;&lt;/tt&gt; n’a pas d’effet.
De même, la directive &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;frame-src&lt;/span&gt;&lt;/tt&gt; de &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;Content-Security-Policy&lt;/span&gt;&lt;/tt&gt; n’a pas d’effet au sein d’un élément &lt;tt class="docutils literal"&gt;meta&lt;/tt&gt; (contrairement aux autres directives).
Voir &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options"&gt;X-Frame-Options header&lt;/a&gt; et &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-src"&gt;Content-Security-Policy: frame-src directive&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tant que ces entêtes ne pourront pas être utilisées, j’ai décidé d’utiliser ce code javascript pour afficher une page vide quand le site est inclus dans une iframe (pour les utilisateurs qui ont javascript activé, ce qui devrait être la plupart des utilisateurs de nos jours) :&lt;/p&gt;
&lt;pre class="code javascript last literal-block"&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'about:blank'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="Blog"></category></entry><entry><title>From matomo to plausible</title><link href="https://www.jujens.eu/posts/en/2025/Mar/09/from-matomo-to-plausible/" rel="alternate"></link><published>2025-03-09T00:00:00+01:00</published><updated>2025-03-09T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-03-09:/posts/en/2025/Mar/09/from-matomo-to-plausible/</id><summary type="html">&lt;p&gt;For several years, I hosted &lt;a class="reference external" href="https://matomo.org/matomo-on-premise/"&gt;my own Matomo instance&lt;/a&gt; to track traffic on my blog and projects while respecting the privacy of my users.
It worked well, but it was the only service I had running on MariaDB.
All the other ones are running on PostgreSQL.
And I had several …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For several years, I hosted &lt;a class="reference external" href="https://matomo.org/matomo-on-premise/"&gt;my own Matomo instance&lt;/a&gt; to track traffic on my blog and projects while respecting the privacy of my users.
It worked well, but it was the only service I had running on MariaDB.
All the other ones are running on PostgreSQL.
And I had several issues with MariaDB and file corruption over the years, mostly on unexpected server shutdown.
Most of the time, I managed to recover the data, but I also lost some if it, sometimes just because MariaDB couldn’t start so Matomo was down for a while.
Regarding Matomo itself, it is very feature complete, but is resource intensive (and thus a bit slow on my server).
I also always found the interface a bit messy.&lt;/p&gt;
&lt;p&gt;Since I’m in the process of cleaning up my servers, I wandered if I could find an alterative.
Ideally, I want a free software I can self host.
I’d like the data to be in my PostgreSQL instance and the project itself will run in a container (so no barrier regarding runtime language).
I had to objection paying a small fee for a good service if needed: I can afford it and it could make my life way easier.
Whatever I choose, I wanted something RGPD compatible that respects the privacy of my users.&lt;/p&gt;
&lt;p&gt;After some research, I found three possible options:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/matomo-org/matomo"&gt;Matomo&lt;/a&gt; which I don’t want to self host any more.
Their paid plan starts at 22€/month which I find expensive given my very small usage.
On the other side, it’s feature complete, privacy focussed and is well known.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/umami-software/umami"&gt;Umami&lt;/a&gt; an MIT project written in TypeScript.
It supports multiple database including PostgreSQL.
It is developed by a US company.
It has a free plan that would suit my needs.
The next tier starts at $20/month.
They don’t rely on cookies to track users but I didn’t find their documentation more details about how they comply with the RGPD or how they respect users privacy.
And I failed to find where the data is hosted for their Cloud plan.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/plausible/analytics"&gt;Plausible&lt;/a&gt; an AGPLv3 project written in Elixir.
In the many list of projects, it’s the only one I had heard of before hand.
While it supports PostgreSQL, it also requires &lt;a class="reference external" href="https://clickhouse.com"&gt;ClickHouse&lt;/a&gt; (a no-sql database designed for analytics).
Running the full stack with docker compose worked well, but it’s probably not the easiest one to self-host.
Their cheapest plan (which fits my needs) starts at 9€/month which is acceptable for me.
It is developed by a European company and their cloud service is hosted in Europe by Hetzner a German hosting provider.
Furthermore, their documentation is very clear and complete regarding how they respect the RGPD and users privacy.
It’s not the most feature complete, but it has just what I need and is also efficient and simple to use.
Their tracking script is also very lightweight.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I started by using the trial the trial period to easily test the tool.
I ran it in parallel with my self hosted Matomo to spot any differences.
Since they don’t track recurring users the same way, I had some differences in the stats.
What’s important is that they were close enough to be considered similar in both tools.&lt;/p&gt;
&lt;p&gt;Since I was happy with the new tool, I went for a paid plan to make my life easier.
No regret so far.
I mostly look at stats once a month with their monthly email report anyway.&lt;/p&gt;
&lt;p&gt;Before shutting down Matomo completely, I wanted to export data I had it it.
To do this, I used the small Python script below.
It took me a bit of time to write and required some trial and errors on my end, so I want to share it in case you are doing something similar.
I didn’t bother importing the data in Plausible: most of it is old and I only want to keep it as archive.&lt;/p&gt;
&lt;pre class="code Python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dateutil.relativedelta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Replace with your values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;api_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;  &lt;span class="c1"&gt;# In my case: https://piwik.jujens.eu/index.php&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;auth_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# That's the tricky part. Matomo has an API, and I thought I’d be able to do GET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# request to it to get my data. It failed. I tried the POST API with a dict&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# and it fail again.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# In the end, the solution I found it to split the suggested GET parameters,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# put it in a dict and do post requests to the API.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;module=API&amp;amp;format=CSV&amp;amp;idSite=1&amp;amp;period=month&amp;amp;date=2024-12-01&amp;amp;method=Actions.getPageTitles&amp;amp;expanded=1&amp;amp;translateColumnNames=1&amp;amp;language=en&amp;amp;token_auth=XXXXX&amp;amp;filter_limit=-1&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;=&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;page_views_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;relativedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
</content><category term="Blog"></category><category term="Web"></category></entry><entry><title>From ZSH to Fish</title><link href="https://www.jujens.eu/posts/en/2021/Nov/07/from-zsh-to-fish/" rel="alternate"></link><published>2021-11-07T00:00:00+01:00</published><updated>2021-11-07T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2021-11-07:/posts/en/2021/Nov/07/from-zsh-to-fish/</id><summary type="html">&lt;p&gt;After several years of ZSH with &lt;a class="reference external" href="https://ohmyz.sh/"&gt;oh-my-zsh&lt;/a&gt;, I decided to give &lt;a class="reference external" href="https://fishshell.com/"&gt;fish (Friendly Interactive Shell)&lt;/a&gt;.
I heard and read several good things about it.
I also wanted to test &lt;a class="reference external" href="https://starship.rs/"&gt;starship&lt;/a&gt; (a cross shell prompt) easily without messing with my ZSH configuration.&lt;/p&gt;
&lt;p&gt;The least I can say is: I'm impressed.
Most …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After several years of ZSH with &lt;a class="reference external" href="https://ohmyz.sh/"&gt;oh-my-zsh&lt;/a&gt;, I decided to give &lt;a class="reference external" href="https://fishshell.com/"&gt;fish (Friendly Interactive Shell)&lt;/a&gt;.
I heard and read several good things about it.
I also wanted to test &lt;a class="reference external" href="https://starship.rs/"&gt;starship&lt;/a&gt; (a cross shell prompt) easily without messing with my ZSH configuration.&lt;/p&gt;
&lt;p&gt;The least I can say is: I'm impressed.
Most of the niceties that ohmyzsh brings simply work out of the box: better history, better completion, syntax coloring…
Since Fish don't use the standard shell syntax, I had to port some of my functions to get a usable shell.
I must admit the changes made by Fish, although a bit hard to get used to after so many years, are really nice and make syntax clearer.
And you can document them!&lt;/p&gt;
&lt;p&gt;Regarding starship, it provides a lot of configuration and options but the default are very good.
Again, the migration was swift and painless.
I merely disabled the AWS and GCP plugins to avoid cluttering my shell with data I don't often need to get the same behavior as my old ZSH configuration.&lt;/p&gt;
&lt;p&gt;To conclude, I'm impressed and very satisfied with my migration.
It went smoothly and most tools (like starship or git) are compatible with it.
I still have to read the doc from time to time and unlearn years of bashism.
Since Fish is not widespread (yet?), I still use Bash more the scripts that must be shared with other people.&lt;/p&gt;
</content><category term="Blog"></category><category term="Shell"></category><category term="Bash"></category><category term="ZSH"></category><category term="Linux"></category></entry><entry><title>Small API to manage packages</title><link href="https://www.jujens.eu/posts/en/2021/Jul/10/small-packages-api/" rel="alternate"></link><published>2021-07-10T00:00:00+02:00</published><updated>2021-07-10T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2021-07-10:/posts/en/2021/Jul/10/small-packages-api/</id><summary type="html">&lt;p&gt;At last, here is my follow up to &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2021/Mar/06/small-todo-apps/"&gt;small TODO apps&lt;/a&gt; where I created a small CLI TODO app in Rust, Clojure and Haskell to test functional (or functional like) programming languages.
Like I said in my previous article, I also wanted to see how these languages behave for the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At last, here is my follow up to &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2021/Mar/06/small-todo-apps/"&gt;small TODO apps&lt;/a&gt; where I created a small CLI TODO app in Rust, Clojure and Haskell to test functional (or functional like) programming languages.
Like I said in my previous article, I also wanted to see how these languages behave for the web.&lt;/p&gt;
&lt;p&gt;So I decided to create the same API four times: first in Python with Django to have a starting point (that's the tech I'm most at ease with) then in Rust, Clojure and Haskell.
This API must be able to add a package, list all packages and search for a given package by name.
The creation of a package should be protected by authentication.
In the end, I only reached this full scope with Django.
I removed authentication in Rust and Clojure to gain time.&lt;/p&gt;
&lt;p&gt;As for Haskell, I completely dropped it: I tried the frameworks &lt;a class="reference external" href="https://www.yesodweb.com/"&gt;Yesdo&lt;/a&gt; and &lt;a class="reference external" href="https://docs.servant.dev"&gt;Servant&lt;/a&gt;.
I had issues to compile the quickstart project with both of them.
I managed to get Servant to work, but struggled with database access.
I also found it difficult to get back in Haskell after such a long time.
I could have searched more or reached for help on an Haskell forum, but the amount of time I was willing to dedicate to this exercise was running out.
So I dropped Haskell and moved on.&lt;/p&gt;
&lt;p&gt;All the code is available &lt;a class="reference external" href="https://gitlab.com/Jenselme/packages-api"&gt;in my gitlab&lt;/a&gt;.
In each project, you'll find a &lt;tt class="docutils literal"&gt;README&lt;/tt&gt; that explains how to launch and use the project.
Like in my previous post, I'll go over the main characteristics of each implementation and then try to conclude.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#python-and-django" id="toc-entry-1"&gt;Python and Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#rust-and-actix" id="toc-entry-2"&gt;Rust and actix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#clojure-and-luminus" id="toc-entry-3"&gt;Clojure and Luminus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#summary" id="toc-entry-4"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="python-and-django"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Python and Django&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wrote the first implementation in &lt;a class="reference external" href="https://docs.djangoproject.com/en/3.2/"&gt;Django&lt;/a&gt; and &lt;a class="reference external" href="https://www.django-rest-framework.org/"&gt;Django Rest Framework&lt;/a&gt;  to have a base implementation I could compare against.&lt;/p&gt;
&lt;p&gt;The project is separated in different apps like in all Django project.
It's not strictly necessary for a project so small, but it's how you do things.
I based the project on &lt;a class="reference external" href="https://www.django-rest-framework.org/tutorial/quickstart/"&gt;the official tutorial&lt;/a&gt; instead of something more complete (like &lt;a class="reference external" href="https://github.com/pydanny/cookiecutter-django"&gt;this generator&lt;/a&gt;) to keep things simple.
I merely changed the app organization a bit to have something a bit more structured.
In the end, I have two apps: &lt;tt class="docutils literal"&gt;core&lt;/tt&gt; to manage users and their authentication and &lt;tt class="docutils literal"&gt;packages&lt;/tt&gt; for the packages.&lt;/p&gt;
&lt;p&gt;My code is structured around three concepts:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/packages-api/-/blob/main/django_drf/package_api/apps/packages/models.py"&gt;The model&lt;/a&gt; for database access.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/packages-api/-/blob/main/django_drf/package_api/apps/packages/serializer.py"&gt;The serializer&lt;/a&gt; to transform the model into JSON and validate inputs when we create a new package.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/packages-api/-/blob/main/django_drf/package_api/apps/packages/views.py"&gt;The view&lt;/a&gt; to glue everything together. It receives the HTTP request, creates the serializer, appends the current user id to the data when we create a package and filters the data when we do a search. The filters part could have been extracted elsewhere but it felt overkill for such a simple project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While writing the apps, I wandered what it would be like if I search with a &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; (it has a special meaning in SQL) character in the same like &lt;tt class="docutils literal"&gt;100%&lt;/tt&gt;.
I created a package named &lt;em&gt;To 100% and beyond&lt;/em&gt; and one named &lt;em&gt;Over 100°C water&lt;/em&gt; and search for articles that contained &lt;tt class="docutils literal"&gt;100%&lt;/tt&gt; (yes, those names are silly for packages).
Django ORM correctly and automatically escaped &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; from my input and the view only returned the &lt;em&gt;To 100% and beyond&lt;/em&gt; package.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rust-and-actix"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Rust and actix&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For my Rust version I decided to use the &lt;a class="reference external" href="https://actix.rs/"&gt;actix&lt;/a&gt; mostly because I already used it a bit in the past.
I was also able to read the example project &lt;a class="reference external" href="https://github.com/actix/examples/tree/master/database_interactions/sqlx_todo"&gt;sqlx-todo&lt;/a&gt; which uses the same tech I did: rust, actix and a sqlite database.&lt;/p&gt;
&lt;p&gt;While I could have used an ORM with actix, I decided not to: I wanted to test a bit of &lt;tt class="docutils literal"&gt;async&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;await&lt;/tt&gt; in Rust and at this time no ORM is compatible with this programming method.&lt;/p&gt;
&lt;p&gt;I rely on custom Shell scripts to create the database and its tables.
It's more than enough for my small test.
Since I have the same usage as the Django version, I relied on Django to generate the SQL.
And since I initially&amp;nbsp;planned to add authentication, I create a table for users and a link between users and packages.
To simplify my implementation, instead of removing this, I decided to create a sample user which will own all the packages.
This way, from the database perspective, I'm closer to the Django version.&lt;/p&gt;
&lt;p&gt;The code is divided in two files:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;main.rs&lt;/tt&gt; which contains all the view logic.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;model.rs&lt;/tt&gt; where the struct representing a package (the struct can be serialized and deserialized easily with a lib named &lt;tt class="docutils literal"&gt;serde&lt;/tt&gt;) and all the logic relating to the database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, the experience was pleasing.
There's less boilerplate than in any other version which makes the project easier to understand when you get started.
On the downside, it's up to you to organize things correctly when the project grows.
The &lt;em&gt;sqlx-todo&lt;/em&gt; project also helped me to start correctly faster, which was nice.
The only real pain point I had was to correctly serialize the package creation date time: there is documentation on how to do that with the &lt;tt class="docutils literal"&gt;chrono&lt;/tt&gt; library but I struggled to put it all together like I wanted.&lt;/p&gt;
&lt;p&gt;Finally, to answer my question: how does it behave by default with &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; in the input, I reproduced the steps of the Django version.
The answer is: by default &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; won't be escaped and will behave as the special SQL character it is.
It's logical you have to do it yourself since you build the actual query yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="clojure-and-luminus"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Clojure and Luminus&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For Clojure, I decided to use the &lt;a class="reference external" href="https://luminusweb.com/"&gt;Luminus&lt;/a&gt; framework because it's easy to get started with it.
&lt;a class="reference external" href="https://leiningen.org/"&gt;leinigen&lt;/a&gt; (an automation tool for Clojure) makes it really easy to generate a project configured to use a SQLite database.
The downside is: the generated project is quite big and for a newbie like me, it's easy to get lost in all this (like a newbie to Django would be I guess).&lt;/p&gt;
&lt;p&gt;Luminus has an integrated migration system based on manually written SQL files.
So I used that to create my table, once again thanks to SQL generated by Django from its models.
All those files are in &lt;tt class="docutils literal"&gt;clj_luminus/resources/migrations/&lt;/tt&gt;.
There is always a &lt;tt class="docutils literal"&gt;.up.sql&lt;/tt&gt; file to apply the migration and a &lt;tt class="docutils literal"&gt;.down.sql&lt;/tt&gt; to revert it if needed.&lt;/p&gt;
&lt;p&gt;Creating a simple view to list package is simple enough as you can see in &lt;tt class="docutils literal"&gt;clj_luminus/src/clj/clj_luminus/routes/api.clj&lt;/tt&gt; (function &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;list-packages&lt;/span&gt;&lt;/tt&gt;).
It can then easily be linked in the router.
What I find peculiar, is how Luminus handles SQL queries:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;You write all your queries in a file named &lt;tt class="docutils literal"&gt;clj_luminus/resources/sql/queries.sql&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Above the query, you give it a name and describe what it does (query, update, insert) in a comment.&lt;/li&gt;
&lt;li&gt;Based on that, HugSQL (the library used by Luminus to do its SQL requests) will automatically create a function to actually do the query.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's a bit strange when you are not used to it, but it works quite well.
The big downside I didn't manage to overcome though: while a change in the code is immediately reflected in the project and is available with a simple page refresh, changing a SQL query always required me to restart the project.
And project startup in Clojure is slow which is a pain.
For a serious project, this would be a big downside.
I guess there is a work around and my problem is just I didn't find it.&lt;/p&gt;
&lt;p&gt;I also struggled a bit to create a package:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;I add to manually add a middleware so the posted JSON would be correctly parsed. That was not obvious at first.&lt;/li&gt;
&lt;li&gt;I'm not convinced by how I pass the date of creation. I think it could fail on a stricter database than SQLite.&lt;/li&gt;
&lt;li&gt;Getting the last inserted ID was way harder than I though. According to the documentation, there is a way to get it, I can see it in the returned data under a key &lt;tt class="docutils literal"&gt;:last_insert_rowid()&lt;/tt&gt; but I never managed to access it. In the end, I converted the map into a vector and read the proper index from the vector. It's a bit ugly, but it works.&lt;/li&gt;
&lt;li&gt;Unlike with Django and Rust where the data is validated out of the box in the serializer or the struct, I had to do it manually here. It's not a big deal, but you must think of it when you are used to other tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To answer my question: how does it behave by default with &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; in the input, I again reproduced the steps of the Django version.
The answer is, just like for Rust: by default &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; won't be escaped and will behave as the special SQL character it is.
Again, it's logical you have to do it yourself since you build the actual query yourself.&lt;/p&gt;
&lt;p&gt;Before concluding, I'd like to point out that although I didn't add authentication because I lacked time, Luminus provides tooling to add it out of the box.
So it should be fairly straightforward.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Summary&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To conclude, I'll say I enjoyed writing these small apps and too see different languages and ways to do things.
I felt the most at ease with Django since it's the framework I work with day to day.
Rust is fun, the compiler does its best to help you and you can find many resources online.
The language (at least the bases, the language itself is actually quite big and complex) is also fairly easy to pick up if you have a programming background.
Clojure is fun, there are some good projects and tooling out there, although the documentation could sometimes be better.
Since it barely has any syntax, you can forget functions of the standard library after a while but not how to write code in it, despite how different it is from other languages.
And Haskell, well, it's probably the language that I would benefit most from learning: it's very different to what I know and its approach probably would make me a better developer.
However, it's hard to pick up with its syntax and type system, even with a programming background.
And after a while, I forgot most of it.
I don't want to dedicate time for it now, so I won't dig any further.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="Rust"></category><category term="Clojure"></category><category term="Haskell"></category></entry><entry><title>Use a dedicated user to run your database migrations on PostgreSQL</title><link href="https://www.jujens.eu/posts/en/2021/Mar/10/db-user-migrations/" rel="alternate"></link><published>2021-03-10T00:00:00+01:00</published><updated>2021-03-10T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2021-03-10:/posts/en/2021/Mar/10/db-user-migrations/</id><summary type="html">&lt;p&gt;I'll detail here how to use one user to apply your migrations and one to run you site.
This method can be applied to any framework and environment as long as your database is PostgreSQL.
It's of course possible to do with other databases, but the SQL syntax to achieve …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'll detail here how to use one user to apply your migrations and one to run you site.
This method can be applied to any framework and environment as long as your database is PostgreSQL.
It's of course possible to do with other databases, but the SQL syntax to achieve this will be different.&lt;/p&gt;
&lt;p&gt;Before beginning, let's see why we would want to do this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Increased security: by limiting the database operations your site is allowed to make, you reduce the attack surface by applying the principle of least privilege.
Please note that I am only talking about schema updates: your site still needs to do SELECT, INSERT, UPDATE and DELETE to work correctly.
So, you only limit the surface of attack.
If an attacker were to gain access to your database, they would still be able to steal or destroy data.
You thus still need proper protection on your database with SSL, strong passwords and possibly access limitation (like IP whitelisting).&lt;/li&gt;
&lt;li&gt;Reduced error capabilities: if you are to execute some queries (to debug or analyze something) in your production database, you cannot alter the schema.
So if you do make mistakes, they shouldn't be too serious.
Please understand that I don't advise you to run queries directly on your production database (your site with its peer-reviewed code is there for it), but you can need it from time to time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How can we achieve this?
Let's dive in.
You can launch all these commands in a container if you want to try them out.
That's what I did to write this tutorial.
All my &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; commands are written with that in mind, please adapt them if you must connect differently to your database.&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;To begin with, we must create the user that will apply our migrations.
Let's call it &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt;.
We could use the &lt;tt class="docutils literal"&gt;postgres&lt;/tt&gt; user for this, but I think it's better to have a dedicated user without the super privilege role for the reasons explained above.
To do this, connect to the database with &lt;tt class="docutils literal"&gt;psql &lt;span class="pre"&gt;-U&lt;/span&gt; postgres&lt;/tt&gt; and run:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ROLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOGIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATEDB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CREATEROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;LOGIN&lt;/tt&gt; is required to allow us to use this user to connect to a database, &lt;tt class="docutils literal"&gt;CREATEDB&lt;/tt&gt; will allow us to create a database that will be owned by &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;CREATEROLE&lt;/tt&gt; will allow us to create the other user with &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt;.
This way, all our objects will have the proper owner which will make our lives easier: since everything we need to operate on belongs to &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt; we can use this user for all our operations.
You could reduce the permissions of &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt; to limit its scope further, it's just a bit more complex and I won't detail this here.
With all the information here, you would be able to do this easily if you want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Now that our user is created, we can exit the first command prompt with &lt;tt class="docutils literal"&gt;\q&lt;/tt&gt; and connect with our new user with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
psql -U sqlmigrations -d postgres
&lt;/pre&gt;
&lt;p&gt;Since no database exists with the name &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt; we need to specify another existing database like the default &lt;tt class="docutils literal"&gt;postgres&lt;/tt&gt;.
If we don't, our connection will fail with &lt;tt class="docutils literal"&gt;psql: FATAL:&amp;nbsp; database &amp;quot;sqlmigrations&amp;quot; does not exist&lt;/tt&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Now, let's create our database:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;djangosite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;We can now connect to our database with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
psql -U sqlmigrations -d djangosite
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Let's create the user for our site:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ROLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If you use the &lt;tt class="docutils literal"&gt;\du&lt;/tt&gt; command to list our users, you should see this at this point:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Role name   |                         Attributes                         | Member of
---------------+------------------------------------------------------------+-----------
django        |                                                            | {}
postgres      | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
sqlmigrations | Create role, Create DB
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Now, let's give the proper roles to our &lt;tt class="docutils literal"&gt;django&lt;/tt&gt; user.
This always happen in two steps: we apply the change on existing objects then we alter the default options to enforce our rules to new objects.
If you have multiple schemas, you will have to repeat the operation for all of them.
Here, I only cover the &lt;tt class="docutils literal"&gt;public&lt;/tt&gt; schema.&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="c1"&gt;-- Allow our user to run SELECT, INSERT, UPDATE, DELETE queries.
&lt;/span&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;-- Enable this for all new tables.
&lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;-- Allow our user to use SEQUENCES.
-- It's required to insert data with auto-incrementing primary keys for instance.
&lt;/span&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SEQUENCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SEQUENCES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;So far, so good.
We are not completely done yet however.
Our &lt;tt class="docutils literal"&gt;django&lt;/tt&gt; user still can alter its permission which we don't want: what's the point of limiting what a user can do if it can do privilege escalation?
We can avoid this with:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;REVOKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OPTION&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;REVOKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OPTION&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLES&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Last step, we must prevent our &lt;tt class="docutils literal"&gt;django&lt;/tt&gt; user to be able to create table (which is possible by default).
We need to do this in three steps (as far as I know, it's the only way to do this):&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Connect to the database with the &lt;tt class="docutils literal"&gt;postgres&lt;/tt&gt; user (only a super user can do this):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
psql -U postgres -d djangosite
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Revoke the ability to create tables for all users:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="c1"&gt;-- public (lower case) is for the schema public.
-- PUBLIC (upper case) means &amp;quot;for all users&amp;quot;.
&lt;/span&gt;&lt;span class="k"&gt;REVOKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Grant back the ability to create table to the &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt; user (again, only a super user can do this):&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SCHEMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we've done all that, let's test it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;As &lt;tt class="docutils literal"&gt;sqlmigrations&lt;/tt&gt;, you should be able to do what you want.
For instance:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
djangosite=&amp;gt; CREATE TABLE tata(id INTEGER);
CREATE TABLE
djangosite=&amp;gt; ALTER TABLE toto ADD COLUMN toto TEXT;
ALTER TABLE
djangosite=&amp;gt; ALTER TABLE toto DROP COLUMN toto;
ALTER TABLE
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;But the user &lt;tt class="docutils literal"&gt;django&lt;/tt&gt; has restricted access (connect with &lt;tt class="docutils literal"&gt;psql &lt;span class="pre"&gt;-U&lt;/span&gt; django &lt;span class="pre"&gt;-d&lt;/span&gt; djangosite&lt;/tt&gt;):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
djangosite=&amp;gt; SELECT * FROM toto;
id
----
(0 rows)

djangosite=&amp;gt; INSERT INTO toto VALUES (1);
INSERT 0 1
djangosite=&amp;gt; SELECT * FROM toto;
id
----
1
(1 row)

djangosite=&amp;gt; ALTER TABLE toto ADD COLUMN toto TEXT;
ERROR:  must be owner of table toto
djangosite=&amp;gt; DROP TABLE toto;
ERROR:  must be owner of table toto
djangosite=&amp;gt; CREATE TABLE dj(id INTEGER);
ERROR:  permission denied for schema public
LINE 1: CREATE TABLE dj(id INTEGER);
                    ^
djangosite=&amp;gt; DELETE FROM toto WHERE id = 1;
DELETE 1
djangosite=&amp;gt; SELECT * FROM toto;
id | tata
----+------
2 |
(1 row)
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;To view a summary of all permissions, run:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
djangosite=&amp;gt; \ddp
                    Default access privileges
    Owner     | Schema | Type  |          Access privileges
---------------+--------+-------+-------------------------------------
sqlmigrations |        | table | sqlmigrations=arwdDxt/sqlmigrations+
            |        |       | django=arwd/sqlmigrations
(1 row)

djangosite=&amp;gt; \dp
                                    Access privileges
Schema | Name | Type  |          Access privileges          | Column privileges | Policies
--------+------+-------+-------------------------------------+-------------------+----------
public | toto | table | sqlmigrations=arwdDxt/sqlmigrations+|                   |
    |      |       | django=arwd/sqlmigrations           |                   |
(1 row)
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bonus: if you created everything as the &lt;tt class="docutils literal"&gt;postgres&lt;/tt&gt; user and want to change to another user, you will have to change the owner of the database and its tables with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;djangosite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OWNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;-- Repeat for each table
&lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OWNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;You cannot use &lt;tt class="docutils literal"&gt;REASSIGN OWNED BY postgres TO sqlmigrations;&lt;/tt&gt; to gain time because PostgreSQL doesn't allow you to do that for objects that belongs to the user &lt;tt class="docutils literal"&gt;postgres&lt;/tt&gt; (it would mess up PG since many system objects are owned and must be owned by the user &lt;tt class="docutils literal"&gt;postgres&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;Some resources:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.postgresql.org/docs/13/sql-grant.html"&gt;The documentation for GRANT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.postgresql.org/docs/13/sql-revoke.html"&gt;The documentation for REVOKE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.postgresql.org/docs/13/sql-alterdefaultprivileges.html"&gt;The documentation for ALTER DEFAULT PRIVILEGES&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.postgresql.org/docs/13/sql-createrole.html"&gt;The documentation for CREATE ROLE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="Blog"></category><category term="Django"></category><category term="PostgreSQL"></category></entry><entry><title>Small TODO apps</title><link href="https://www.jujens.eu/posts/en/2021/Mar/06/small-todo-apps/" rel="alternate"></link><published>2021-03-06T00:00:00+01:00</published><updated>2021-03-06T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2021-03-06:/posts/en/2021/Mar/06/small-todo-apps/</id><summary type="html">&lt;p&gt;I recently decided to lean a bit of Haskell for fun and see what the language was like.
In this context, I decided to create a small TODO app.
Since I also wanted to compare my solution with solutions in other languages, I decided to write the TODO app three …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently decided to lean a bit of Haskell for fun and see what the language was like.
In this context, I decided to create a small TODO app.
Since I also wanted to compare my solution with solutions in other languages, I decided to write the TODO app three times:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;In Haskell to practice the language as well as monads.&lt;/li&gt;
&lt;li&gt;In Rust because I like the language.&lt;/li&gt;
&lt;li&gt;In Clojure because I already knew it and I wanted to practice it.
It's a functional language like Haskell, so I though it would be interesting to include it in the comparison.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each app will do the exact same thing:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Initialize the database on startup, this means create the SQLite file and the table &lt;tt class="docutils literal"&gt;todo&lt;/tt&gt; if required.
This way, we can easily use it later.&lt;/li&gt;
&lt;li&gt;Allow the database file to be specified to use a specific file if needed.&lt;/li&gt;
&lt;li&gt;Store new TODO items in the database. Each TODO will be made of an id, a title and a completion status (ie done or not done).&lt;/li&gt;
&lt;li&gt;List all TODOs.&lt;/li&gt;
&lt;li&gt;Insert test data in the database.&lt;/li&gt;
&lt;li&gt;Mark a TODO as done.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'll talk a bit about each app and then try to conclude my experience.
I'll start with the Rust one, then the Clojure one and finally the Haskell one (that's the order in which I wrote them).
You will find the full source code in each section.&lt;/p&gt;
&lt;div class="section" id="rust"&gt;
&lt;h2&gt;Rust&lt;/h2&gt;
&lt;p&gt;Rust is the language that most alike to the languages I use everyday: Python and JavaScript.
The goal of the language is to write fast and memory safe code without the need for a garbage collector.
It's done thanks to its compiler and its type system (which I won't explain here).
The errors of the compiler are generally explicit and clear.
While it borrows some concepts from functional programming like immutability by default or &lt;tt class="docutils literal"&gt;if&lt;/tt&gt; expressions, we can still write procedural or object like code.
We also have to make some variables mutable from time to time (which is not possible in functional programming languages).
So it's a mix, but an efficient one if you ask me.&lt;/p&gt;
&lt;p&gt;I also think the community is striving and I found without any difficulty many libraries to parse command line arguments or communicate with the database.
I settled with &lt;a class="reference external" href="https://github.com/clap-rs/clap"&gt;clap&lt;/a&gt; for argument parsing and &lt;a class="reference external" href="https://github.com/rusqlite/rusqlite/"&gt;rusqlite&lt;/a&gt; for SQLite communication.
They both seems like popular and robust choices for what I was aiming for while staying simple to use.&lt;/p&gt;
&lt;p&gt;Now, let's talk about the code itself.
The definition of the parser resembles what I could do in Python or JS, as you can see with the code sample below.
I also find it very readable and expressive.&lt;/p&gt;
&lt;pre class="code rust literal-block"&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Todo application&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Jujens &amp;lt;jujens&amp;#64;jujens.eu&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Small app to manage todos in a local SQLite database.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SubcommandRequiredElseHelp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;with_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;takes_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_DB_PATH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Path to the database.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subcommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Insert test TODOs in the database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Since I have 4 different actions with different arguments, I decided to divide the code in subcommands.
A pattern I reused for all apps.&lt;/p&gt;
&lt;p&gt;If creating the parser is very similar to what I could have done in Python, when I had to use it, I saw some particularities of Rust: Rust doesn't have exceptions and forces you to handle all errors.
For instance, when I create a TODO in &lt;tt class="docutils literal"&gt;handle_create&lt;/tt&gt;, I pass a title with the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--title&lt;/span&gt;&lt;/tt&gt; option.
Since the argument may not be provided, I get an &lt;tt class="docutils literal"&gt;Option&lt;/tt&gt; from the parser: it's a data structure used in Rust to handle optional values, like &lt;tt class="docutils literal"&gt;Maybe&lt;/tt&gt; in Haskell.
When you have this structure, you need to extract the value from it before you can use it which forces you to handle the case where you have a value and the case where you haven't one.
So the compiler, forces you to handle potential missing data.
There is also a similar structure named &lt;tt class="docutils literal"&gt;Result&lt;/tt&gt; which holds a value or an error.
It's useful when you need to provide a useful error message and not just a value or &lt;tt class="docutils literal"&gt;None&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;This is how it looks like:&lt;/p&gt;
&lt;pre class="code rust literal-block"&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;You must supply a title for the match&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This is very interesting and allowed me to provide custom and precise error messages at each step.
But, this makes the code quite verbose.
To help a bit, you can use things like &lt;tt class="docutils literal"&gt;unwrap&lt;/tt&gt; (which can result in a crash if the structure doesn't hold a value) or &lt;tt class="docutils literal"&gt;unwrap_or&lt;/tt&gt; to extract the value or get a default value if needed.
You can also use the &lt;tt class="docutils literal"&gt;?&lt;/tt&gt; operator like that:&lt;/p&gt;
&lt;pre class="code rust literal-block"&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) VALUES (?1, ?2)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The compiler will propagate the error, meaning the compiler will replace the code above by:&lt;/p&gt;
&lt;pre class="code rust literal-block"&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) VALUES (?1, ?2)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;which is much more compact and achieve the same result if you don't want/need to handle the error at call site.
That's a very nice syntactic sugar I&amp;nbsp;didn't used much to handle errors in order to provide the user with relevant messages.&lt;/p&gt;
&lt;p&gt;The thing to remember (from my perspective at least) is that you can write code that crash at runtime if you don't pay attention or don't know what you are doing.
The language will just give you tools to avoid this, it's then up to you to use those tools correctly.&lt;/p&gt;
&lt;p&gt;Since all my other commands are written in the same way, I won't detail more: I think I have given you the gist of the code.
To dig deeper, here is my &lt;tt class="docutils literal"&gt;Cargo.toml&lt;/tt&gt; file with the project dependencies:&lt;/p&gt;
&lt;pre class="code toml literal-block" id="cargo-toml"&gt;
&lt;span class="ln"&gt; 1 &lt;/span&gt;&lt;span class="k"&gt;[package]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 2 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;todos&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 3 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 4 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;authors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Julien Enselme &amp;lt;jenselme&amp;#64;jujens.eu&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 5 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;edition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2018&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 6 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 7 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;[dependencies]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 8 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;clap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2.33.3&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 9 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;10 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;11 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;12 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;[dependencies.rusqlite]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;13 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.24.2&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;14 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bundled&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;and the full &lt;tt class="docutils literal"&gt;src/main.rs&lt;/tt&gt; file:&lt;/p&gt;
&lt;pre class="code rust literal-block" id="src-main-rs"&gt;
&lt;span class="ln"&gt;  1 &lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  2 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  3 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  4 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clap&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ArgMatches&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  5 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  6 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rusqlite&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  7 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rusqlite&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NO_PARAMS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  8 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  9 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="cp"&gt;#[derive(Debug)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 10 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 11 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 12 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 13 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 14 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 15 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 16 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 17 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEFAULT_DB_PATH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./todos.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 18 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Todo application&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 19 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Jujens &amp;lt;jujens&amp;#64;jujens.eu&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 20 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Small app to manage todos in a local SQLite database.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 21 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SubcommandRequiredElseHelp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 22 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 23 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;with_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 24 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 25 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 26 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;takes_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 27 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_DB_PATH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 28 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Path to the database.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 29 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 30 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subcommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 31 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 32 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Insert test TODOs in the database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 33 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 34 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subcommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 35 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 36 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;List all TODOs from the database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 37 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 38 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subcommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 39 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 40 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Create a new TODOs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 41 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArgRequiredElseHelp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 42 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 43 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;with_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 44 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;t&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 45 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 46 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;takes_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 47 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 48 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multiple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 49 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The title of the TODO to use.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 50 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 51 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 52 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subcommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 53 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 54 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Mark a TODO as done&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 55 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArgRequiredElseHelp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 56 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 57 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;with_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 58 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 59 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The ID of the TODO to complete.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 60 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 61 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 62 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_matches&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 63 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 64 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_DB_PATH&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 65 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 66 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 67 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;An error occurred while initializing the database: &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 68 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 69 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 70 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 71 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 72 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subcommand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 73 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle_insert_test_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 74 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle_list_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 75 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;creation_matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;creation_matches&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 76 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;complete_matches&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;complete_matches&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 77 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 78 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No valid subcommand was used&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 79 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 80 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 81 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 82 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 83 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 84 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 85 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 86 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 87 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 88 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 89 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 90 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;CREATE TABLE IF NOT EXISTS todo (id integer primary key, title text not null, is_done boolean not null)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NO_PARAMS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 91 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 92 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 93 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 94 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 95 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 96 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 97 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 98 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 99 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_insert_test_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;100 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insert_test_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;101 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;102 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;103 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;An error occurred while inserting test data: &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;104 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;105 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;106 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;107 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;108 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;109 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;insert_test_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;110 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;111 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;112 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;DELETE FROM todo;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NO_PARAMS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;113 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) VALUES (?1, ?2)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Start project&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;114 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) VALUES (?1, ?2)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Complete project&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;115 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;116 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;117 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;118 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;119 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_list_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;120 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;121 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;122 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;123 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;An error occurred while listing all todos: &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;124 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;125 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;126 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;127 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;128 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;129 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;display_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;130 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;131 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;- id: {}, title: &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;, done: {}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_done&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;132 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;133 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;134 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;135 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;list_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;136 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SELECT id, title, is_done FROM todo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;137 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todos_iter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NO_PARAMS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;138 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;139 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;140 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;141 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;is_done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;142 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;143 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;144 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;145 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;146 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo_result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todos_iter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;147 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo_result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;148 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;149 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Error while fetching some todos: &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;150 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;151 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;152 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;153 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;154 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;155 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;156 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create_matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ArgMatches&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;157 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create_matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;158 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;159 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;160 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;You must supply a title for the match.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;161 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;162 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;163 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;164 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;165 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;166 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;167 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;You must supply a title for the match&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;168 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;169 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;170 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;171 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;172 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;173 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;174 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;175 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;An error occurred while creating a new TODO: &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;176 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;177 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;178 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;179 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;180 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;181 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;182 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) VALUES (?1, ?2)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;183 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;184 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;185 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;186 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;187 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;complete_matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;ArgMatches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;188 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;complete_matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;189 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;190 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;191 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;You must supply an id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;192 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;193 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;194 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;195 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;196 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;197 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;198 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;199 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;You must supply a valid ID (an interger): &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;200 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;201 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;202 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;203 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;204 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;205 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;206 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;207 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;An error occurred while completing the TODO {}: &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;208 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;209 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;210 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;211 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;212 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;213 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nb_rows_updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;UPDATE todo SET is_done = ?1 WHERE id = ?2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;214 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;215 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nb_rows_updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;216 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="fm"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No row found for supplied id: {}.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;217 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="fm"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;218 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(()),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;219 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;220 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="clojure"&gt;
&lt;h2&gt;Clojure&lt;/h2&gt;
&lt;p&gt;Clojure is very different.
It's a LISP where all the code is written as lists (like in all LISPs) that runs on the JVM.
All data structures are immutable and variables are dynamically typed.
The language aims to focus on the data instead of the program.&lt;/p&gt;
&lt;p&gt;It was easy to find libraries but I found that the parser was less complete than the Rust or Haskell one (or Python ones for that matter).
I guess it comes from the fact that Clojure is a niche language that is less known and has a smaller community.&lt;/p&gt;
&lt;p&gt;I had more manual work to do to parse my arguments, including the validation.
I think that when you are not used to the language or another LISP, the program is hard to decode with everything having the same structure.
Since it's also very different, it's also way harder to understand if you don't already know the language.&lt;/p&gt;
&lt;p&gt;I defined the options of the parser in a vector (similar to a list in Python):&lt;/p&gt;
&lt;pre class="code Clojure literal-block"&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;cli-options&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-f&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--file PORT&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Path to the database file to use.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./todos.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-h&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I can then use this with the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;parse-opts&lt;/span&gt;&lt;/tt&gt; function to extract my arguments (and potential errors):&lt;/p&gt;
&lt;pre class="code Clojure literal-block"&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cli-options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:in-order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;parse-opts&lt;/span&gt;&lt;/tt&gt; function will return a map (a dict in Python), the &lt;tt class="docutils literal"&gt;:keys&lt;/tt&gt; allows me to extract values from this map (&lt;tt class="docutils literal"&gt;options&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;argument&lt;/tt&gt;…) and the &lt;tt class="docutils literal"&gt;let&lt;/tt&gt; expression allows me to bind these values to local variables (with the same name they had in the map).
I can then use these values to build a map either to exit the program later with a message or to execute actual code based on the supplied arguments.
I used a similar pattern to define and parse the options of my subcommands.&lt;/p&gt;
&lt;p&gt;I won't detail much here since I think it would take too much time to teach Clojure here.
I just say that all the fonctions themselves are way shorter than in the Rust version since I could easily handle all exceptions globally with a catch handler (Clojure relies on exception for errors).
The price is: I'm less precise in the messages I give.
But I could avoid this by adding more catch handlers at the price of verbosity.
I'll also point out that all functions that ends with an exclamation mark like &lt;tt class="docutils literal"&gt;insert!&lt;/tt&gt; mutate the sate by convention and must not be confused by by the exclamation mark used to identify macros in Rust.
With that, you should be able to get a high level understanding of what the program does.&lt;/p&gt;
&lt;p&gt;Like above, here is my &lt;tt class="docutils literal"&gt;project.clj&lt;/tt&gt; with project dependencies:&lt;/p&gt;
&lt;pre class="code Clojure literal-block" id="project-clj"&gt;
&lt;span class="ln"&gt; 1 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defproject &lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.1.0-SNAPSHOT&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 2 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;FIXME: write description&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 3 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://example.com/FIXME&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 4 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;:license&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 5 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://www.eclipse.org/legal/epl-2.0/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 6 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;:dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;org.clojure/clojure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.10.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 7 &lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;org.clojure/tools.cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0.194&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 8 &lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;org.clojure/java.jdbc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.7.12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 9 &lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;org.xerial/sqlite-jdbc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.34.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;10 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;:main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="ss"&gt;:skip-aot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;todo.core&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;11 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;:target-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;target/%s&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;12 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;:profiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:uberjar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:aot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:all&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;and my &lt;tt class="docutils literal"&gt;src/todo/core.clj&lt;/tt&gt; source file:&lt;/p&gt;
&lt;pre class="code Clojure literal-block" id="src-todo-core-clj"&gt;
&lt;span class="ln"&gt;  1 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;ns &lt;/span&gt;&lt;span class="nv"&gt;todo.core&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  2 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;clojure.tools.cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;parse-opts&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  3 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;clojure.string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  4 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;clojure.java.jdbc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jdbc&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  5 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:gen-class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  6 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clojure.lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ExceptionInfo&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  7 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  8 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;allowed-commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;  9 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 10 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;command-&amp;gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 11 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-h&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 12 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-h&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 13 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-h&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 14 &lt;/span&gt;&lt;span class="w"&gt;                                 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-t&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--title TITLE&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The title of the todo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 15 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;complete&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-h&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 16 &lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--id ID&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The ID of the TODO to mark as done&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 17 &lt;/span&gt;&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="ss"&gt;:parse-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Integer/parseInt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 18 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 19 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 20 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;command-&amp;gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 21 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Insert test TODOs into the database&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 22 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;List all TODOs from the database&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 23 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Create a new TODO&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 24 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;complete&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Mark the TODO as done&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 25 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 26 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 27 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;cli-options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 28 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-f&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--file PORT&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Path to the database file to use.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 29 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./todos.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 30 &lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-h&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 31 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 32 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;usage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;options-summary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 33 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;This is a sample TODO program to create and update TODOs&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 34 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Usage: todo [options] COMMAND [command-options]&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 35 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 36 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Options:&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 37 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;options-summary&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 38 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 39 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Commands:&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 40 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;string/join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;ewline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;allowed-commands&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 41 &lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;string/join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;ewline&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 42 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 43 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;error-msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 44 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The following errors occurred while parsing your command:\n&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 45 &lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;string/join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;ewline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 46 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 47 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 48 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println &lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 49 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 50 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 51 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;command-usage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 52 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;string/join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;ewline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nf"&gt;command-&amp;gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 53 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 54 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;validate-command-args&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 55 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Validate arguments for each subcommands&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 56 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global-options&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 57 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 58 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;command-args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rest &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 59 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;command-&amp;gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 60 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cond&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 61 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit-message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;error-msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ok?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 62 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit-message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;command-usage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ok?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 63 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="ss"&gt;:else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:global-options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global-options&lt;/span&gt;&lt;span class="p"&gt;})))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 64 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 65 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;validate-args&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 66 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Validate command line arguments. Either return a map indicating the program
&lt;/span&gt;&lt;span class="ln"&gt; 67 &lt;/span&gt;&lt;span class="s"&gt;  should exit (with a error message, and optional ok status), or a map
&lt;/span&gt;&lt;span class="ln"&gt; 68 &lt;/span&gt;&lt;span class="s"&gt;  indicating the action the program should take and the options provided.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 69 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 70 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cli-options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:in-order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 71 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cond&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 72 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit-message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;usage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ok?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 73 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit-message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;error-msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ok?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 74 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;= &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count &lt;/span&gt;&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 75 &lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;allowed-commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validate-command-args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 76 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="ss"&gt;:else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit-message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;usage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)})))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 77 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 78 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;insert-test-data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 79 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 80 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jdbc/insert-multi!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:todo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 81 &lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:done&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 82 &lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Start the project&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 83 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Complete the project&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 84 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 85 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;display-todo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 86 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 87 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nb"&gt;str &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;- id: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 88 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;title: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 89 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;done: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;= &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 90 &lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;string/join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 91 &lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 92 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 93 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;list-all&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 94 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 95 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jdbc/query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SELECT id, title, done FROM todo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 96 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;doseq &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 97 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;display-todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 98 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 99 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;create&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;100 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;101 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;if-let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;102 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jdbc/insert!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;103 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;error-msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;You must supply a title with --title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;104 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;105 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;complete&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;106 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;107 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;if-let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;108 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;nb-rows-updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jdbc/update!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;id = ?&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]))]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;109 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;not &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;= &lt;/span&gt;&lt;span class="nv"&gt;nb-rows-updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;110 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No rows could be found for id &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;111 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;error-msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;You must supply the id of the TODO with --id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;112 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;113 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;init&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;114 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;115 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jdbc/db-do-commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;116 &lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jdbc/create-table-ddl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:integer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:primary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;117 &lt;/span&gt;&lt;span class="w"&gt;                                                     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;not null&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;118 &lt;/span&gt;&lt;span class="w"&gt;                                                     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;not null&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;119 &lt;/span&gt;&lt;span class="w"&gt;                                              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:conditional?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;120 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;121 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn- &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;122 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;global-options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;123 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:classname&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;org.sqlite.JDBC&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;124 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="ss"&gt;:subprotocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sqlite&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;125 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="ss"&gt;:subname&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global-options&lt;/span&gt;&lt;span class="p"&gt;)}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;126 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jdbc/with-db-transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;127 &lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;128 &lt;/span&gt;&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;129 &lt;/span&gt;&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;130 &lt;/span&gt;&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;insert-test-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;131 &lt;/span&gt;&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list-all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;132 &lt;/span&gt;&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;133 &lt;/span&gt;&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;complete&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;134 &lt;/span&gt;&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;135 &lt;/span&gt;&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;136 &lt;/span&gt;&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;An exception occurred while performing &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;137 &lt;/span&gt;&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; with options &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;138 &lt;/span&gt;&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; and global options &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global-options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;139 &lt;/span&gt;&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;140 &lt;/span&gt;&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.getMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)))))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;141 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;142 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;-main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;143 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;144 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global-options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;exit-message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ok?&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validate-args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;145 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nv"&gt;exit-message&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;146 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nv"&gt;ok?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;exit-message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;147 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global-options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="haskell"&gt;
&lt;h2&gt;Haskell&lt;/h2&gt;
&lt;p&gt;Haskell is also very different from other languages I normally use and it's often cited as the reference of functional languages.
In Haskell, functions are more like mathematical functions, all variables are immutable and all side effects are encapsulated to maintain function purity.
It also relies on indentation to express the code and don't need semi-colon or curly braces or to wrap everything in parentheses.
The type system is very expressive and is a bit hard to read and understand when you start (mostly when many Monads are involved in functions signatures).
Like for Clojure, I'll analyse part of the code but won't attempt to teach you Haskell.&lt;/p&gt;
&lt;p&gt;The parsing of the arguments relies heavily on the type system and on the combination of function.
For instance, to parse the title used to create a TODO, first I create a union type to hold all the commands (&lt;tt class="docutils literal"&gt;deriving Show&lt;/tt&gt; is an easy way to be able to print the data):&lt;/p&gt;
&lt;pre class="code Haskell literal-block"&gt;
&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;InsertTestData&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;deriving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Then, I create a type named &lt;tt class="docutils literal"&gt;CreateOptions&lt;/tt&gt; to hold the options:&lt;/p&gt;
&lt;pre class="code Haskell literal-block"&gt;
&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;then a function to parse the option themselves:&lt;/p&gt;
&lt;pre class="code Haskell literal-block"&gt;
&lt;span class="nf"&gt;createOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;createOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strOption&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;short&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The title of the TODO.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metavar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;TITLE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;That's readable but to really understand what is going on you need to understand what &lt;tt class="docutils literal"&gt;&amp;lt;$&amp;gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&amp;lt;&amp;gt;&lt;/tt&gt; do which isn't simple and which I won't explain there.&lt;/p&gt;
&lt;p&gt;I can then create the parser for the command itself:&lt;/p&gt;
&lt;pre class="code Haskell literal-block"&gt;
&lt;span class="nf"&gt;createParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;createParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createOptionsParser&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;which can then be used in the subcommand parser.&lt;/p&gt;
&lt;p&gt;The arguments will be parsed in a &lt;tt class="docutils literal"&gt;do&lt;/tt&gt; block to handle side effects (another important point I won't explain).&lt;/p&gt;
&lt;p&gt;The most interesting part is the use of pattern matching to run the proper command based on the actual type of the parsed command:&lt;/p&gt;
&lt;pre class="code Haskell literal-block"&gt;
&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listAll&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;InsertTestData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insertTestData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;completeTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The parser will create a &lt;tt class="docutils literal"&gt;Command&lt;/tt&gt; type of &amp;quot;type&amp;quot; &lt;tt class="docutils literal"&gt;List&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;Create&lt;/tt&gt;… and we will use this syntax to execute specific code depending on the actual type of the command and extract their parameters with destructuring.
I find that very expressive and concise.
And it avoids to have complex &lt;tt class="docutils literal"&gt;if&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;case&lt;/tt&gt;.
I also note that with its usage of parenthesis, Haskell code sometimes makes me thing of LISP.&lt;/p&gt;
&lt;p&gt;Now that I managed to parse the options, I can do things, for instance create a TODO:&lt;/p&gt;
&lt;pre class="code Haskell literal-block"&gt;
&lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;insertResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) values (?, ?)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;False&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Either&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;SomeException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insertResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;of&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;putStrLn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Failed to insert the todo: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The way I handle the errors highly resemble how it's done in Rust.
The &lt;tt class="docutils literal"&gt;try&lt;/tt&gt; function is a way to catch the potential errors from the executed query.
Without it, the code would crash if &lt;tt class="docutils literal"&gt;execute&lt;/tt&gt; encountered an error.
So yes, Haskell code can compile and crash at runtime if you don't pay attention (or don't know what you are doing) in some cases.
I find that a bit weird given the preconception I had about the language which was all errors must be handled and if it compiles it works.
To be fair, this can also happen in Rust, when you try to access an index that doesn't exist in a array or with invalid use of &lt;tt class="docutils literal"&gt;unwrap&lt;/tt&gt; for instance.
I guess no language is the silver bullet in that matter.&lt;/p&gt;
&lt;p&gt;As usual, here's the code of my &lt;tt class="docutils literal"&gt;app/Main.hs&lt;/tt&gt; file which serves as an entry point with parsing logic:&lt;/p&gt;
&lt;pre class="code Haskell literal-block" id="app-main-hs"&gt;
&lt;span class="ln"&gt; 1 &lt;/span&gt;&lt;span class="cm"&gt;{-# LANGUAGE BlockArguments #-}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 2 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 3 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;where&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 4 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 5 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Lib&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 6 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Options.Applicative&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 7 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Data.Semigroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 8 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Database.SQLite.Simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 9 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;10 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;GlobalOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;11 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;12 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;GlobalOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;GlobalOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;deriving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;13 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;14 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;deriving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;15 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;16 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;deriving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;17 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;18 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;19 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;20 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;InsertTestData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;21 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;22 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;23 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;deriving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;24 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;25 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;listParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;26 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;listParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;27 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;28 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;insertTestDataParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;29 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;insertTestDataParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;InsertTestData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;30 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;31 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;createParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;32 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;createParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createOptionsParser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;33 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;34 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;createOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;35 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;createOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;36 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strOption&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;short&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The title of the TODO.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metavar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;TITLE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;37 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;38 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;completeParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;39 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;completeParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;completeOptionsParser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;40 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;41 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;completeOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;42 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;completeOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;43 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metavar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ID&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The ID of the todo to complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;44 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;45 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;globalOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;GlobalOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;46 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;globalOptionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;GlobalOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;47 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strOption&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;short&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;'f'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Path to the database file.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metavar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;FILE&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./todos.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;48 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;49 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;commandParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;50 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;commandParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hsubparser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;51 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;52 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;List all TODOs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;53 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;insert-test-data&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insertTestDataParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Add test TODOs in the database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;54 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Create a new TODO.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;55 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;complete&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;completeParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Mark a TODO as done.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;56 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;57 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;58 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;optionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;59 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;optionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;60 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;globalOptionsParser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;61 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;commandParser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;62 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;63 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;64 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;65 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;where&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;66 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optionsParser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;**&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;67 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fullDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;progDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Manage TODOs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;68 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;69 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;70 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;globalOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;71 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initDb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;72 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;73 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;74 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;75 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listAll&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;76 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;InsertTestData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insertTestData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;77 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CreateOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;78 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;CompleteOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;completeTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;and the &lt;tt class="docutils literal"&gt;src/Lib.hs&lt;/tt&gt; with the rest of the logic:&lt;/p&gt;
&lt;pre class="code Haskell literal-block" id="src-lib-hs"&gt;
&lt;span class="ln"&gt; 1 &lt;/span&gt;&lt;span class="cm"&gt;{-# LANGUAGE DeriveGeneric #-}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 2 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="cm"&gt;{-# LANGUAGE OverloadedStrings #-}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 3 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 4 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Lib&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 5 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;listAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 6 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nf"&gt;initDb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 7 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nf"&gt;insertTestData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 8 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 9 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nf"&gt;completeTodo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;10 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;where&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;11 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;12 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;GHC.Generics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;13 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Control.Monad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;forM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;forM_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;14 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Prelude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;hiding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;15 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Control.Applicative&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;16 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Control.Exception&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;17 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Database.SQLite.Simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;executeNamed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;execute_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;withTransaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;NamedParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;18 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Database.SQLite.Simple.FromRow&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;19 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;20 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;deriving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;21 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;22 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- The number of field here must match the number of fields we get from the SELECT.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;23 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kr"&gt;instance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;FromRow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;where&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;24 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fromRow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;25 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;26 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;initDb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;27 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;initDb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;28 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./todos.db&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- The database file will be created if it doesn't exist.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;29 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;execute_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;CREATE TABLE IF NOT EXISTS todo (id INTEGER PRIMARY KEY, title STR NOT NULL, is_done BOOLEAN NOT NULL)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;30 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;31 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;32 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;insertTestData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;33 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;insertTestData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;34 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;withTransaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;replaceByTestData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;35 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;36 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;replaceByTestData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;37 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;replaceByTestData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;38 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;execute_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;DELETE FROM todo;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;39 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) values (?, ?)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Start project&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;40 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) values (?, ?)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Complete project&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;False&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;41 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;42 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;listAll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;43 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;listAll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;44 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SELECT id, title, is_done FROM todo;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Either&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;SomeException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;45 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;of&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;46 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;putStrLn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Failed to list all todos: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;47 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allTodos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mapM_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;putStrLn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;formatTodo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allTodos&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;48 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;49 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;formatTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;50 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;formatTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isDone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;- id: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;, title: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;, done: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isDone&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;51 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;52 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;53 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;54 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;insertResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INSERT INTO todo (title, is_done) values (?, ?)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;False&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Either&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;SomeException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;55 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insertResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;of&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;56 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;putStrLn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Failed to insert the todo: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;57 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;58 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;59 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;60 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;completeTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;61 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;completeTodo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;62 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;completeResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executeNamed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;UPDATE todo SET is_done = true WHERE id = :id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;:id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todoId&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Either&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;SomeException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;63 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;completeResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;of&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;64 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;putStrLn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Failed to update the todo: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;65 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;66 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;nbUpdatedRows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;67 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;displayUpdateMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nbUpdatedRows&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;68 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;69 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;displayUpdateMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;IO&lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;70 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;displayUpdateMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;putStrLn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Failed to find a todo with the supplied id.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;71 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nf"&gt;displayUpdateMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Here are also my &lt;a class="reference external" href="https://www.jujens.eu/static/small-todo-apps/stack.yaml"&gt;stack.yaml&lt;/a&gt; file and may &lt;a class="reference external" href="https://www.jujens.eu/static/small-todo-apps/package.yaml"&gt;package.yaml&lt;/a&gt;.
I didn't include them here since they are way longer that the other dependencies files and give extra details about how the project must be compiled.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="wrapping-up"&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Some stats to compare each solution:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The full code of the Rust version is 220 lines, but only 164 lines of code when I remove the lines with the closing curly which we don't have in other languages.
It's also the app where I&amp;nbsp;have the most detailed and precise error messages.
It felt right to write it this way.&lt;/li&gt;
&lt;li&gt;The Clojure version is 147 lines long.&lt;/li&gt;
&lt;li&gt;The Haskell version is 149 lines long.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I'd say in terms of code length, they all are pretty much the same, with Rust being a bit more verbose.&lt;/p&gt;
&lt;p&gt;I'd like to say I enjoyed writing all these apps.
The Haskell one was the most challenging to write: it's the language I know the least and is the most different from what I am use to writing.
Their usage of Monad and of &lt;tt class="docutils literal"&gt;IO&lt;/tt&gt; in type signature was (and still is a bit) obscure to me.
I think these signatures can be obvious to someone at ease with Haskell, but as a newcomer, without examples to illustrate how the function actually works, it was hard to understand.
I often had to search the Internet for examples to help me write the code.
I also struggled a bit with the operators &lt;tt class="docutils literal"&gt;&amp;lt;$&amp;gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&amp;lt;*&amp;gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;I'm also a bit disappointed by the Clojure version: the argument parser is less complete than the other ones and I was expecting more of it.
I also still have issues reading the code at time, mostly when I want to scan the code to find something.
Maybe it's just a habit to work on.&lt;/p&gt;
&lt;p&gt;I think the Rust one shines: it's expressive, errors must always be handled correctly and I found many examples as well as solid library to implement what I was trying to do.
This small experiment made the language more interesting for me.
It's definitely the one I'm most exited about although I&amp;nbsp;still don't think I'll use the language much aside from toy project for now: I find it a bit verbose and complex for the web app I am writing.&lt;/p&gt;
&lt;p&gt;I'll try to write a small web API in the following weeks to complete my experience (and for fun) and write an complementary blog post.&lt;/p&gt;
&lt;p&gt;In July, I finally wrote the follow up article: &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2021/Jul/10/small-packages-api/"&gt;Small API to manage packages&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="Rust"></category><category term="Clojure"></category><category term="Haskell"></category></entry><entry><title>Testing a clojure web framework</title><link href="https://www.jujens.eu/posts/en/2020/Aug/02/testing-clojure-luminus/" rel="alternate"></link><published>2020-08-02T00:00:00+02:00</published><updated>2020-08-02T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2020-08-02:/posts/en/2020/Aug/02/testing-clojure-luminus/</id><summary type="html">&lt;p class="first last"&gt;An opinion about Clojure, Luminus and Reagent after doing a test project with these technologies.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;After learning &lt;a class="reference external" href="https://clojure.org/"&gt;Clojure&lt;/a&gt; thanks to the awesome book &lt;a class="reference external" href="https://www.braveclojure.com/"&gt;Clojure for the Brave and True&lt;/a&gt; I decided to test some web programing with the language to practice a bit.&lt;/p&gt;
&lt;p&gt;Before dwelling on this experience, I'd like to say a few words about the language itself.
I heard about the language when I was still completing my masters degree in 2014 or 2015.
I didn't gave it much attention back then.
Later, I noticed that &lt;a class="reference external" href="https://blog.cleancoder.com/"&gt;Robert C. Martin&lt;/a&gt; (author of the book &lt;a class="reference external" href="https://www.goodreads.com/book/show/3735293-clean-code"&gt;Clean Code&lt;/a&gt; among other books) talked about it and seemed to enjoy the language.
Since I respect his technical opinion, it raised my interest again and I look a bit into it and bought the book &lt;em&gt;Clojure for the Brave and True&lt;/em&gt; which then sat on my bookshelf for a couple of years.
Until the middle of last year when I decided to seriously look at functional programming and finally went through the book.&lt;/p&gt;
&lt;p&gt;This was mind blowing!
In addition to the great qualities of the book who made the learning journey really fun, I found the language itself to be really well designed and interesting to learn.
I like the notion than almost everything is a function (the example with the simple &lt;tt class="docutils literal"&gt;+&lt;/tt&gt; function at the beginning of the book really hooked me up), the way it handles &amp;quot;polymorphism&amp;quot;, atoms and references…
I also had to change perspective and get used to everything being immutable which requires lot of thinking at first but forced me to reconsider many things about my daily programing practice.
The fact that the book comes with many exercises after each chapter is a great plus to sharpen the reader's skills.
It's a great learning experience and I do encourage you to learn the language.
I must confess I still have issues with the syntax (it's a LISP after all with lots of parentheses everywhere) and how to organize my code.
But I don't practice much, so I guess it's normal.&lt;/p&gt;
&lt;p&gt;Moving on to web development and Luminus.
In order to practice and to do something that resembles what I do daily (web development), I decided to write a small website using the language.
I looked around for a library/framework to help me in this endeavour and found &lt;a class="reference external" href="https://luminusweb.com/"&gt;Luminus&lt;/a&gt; which looked very good: the documentation is very detailed and it has a great getting started.
It also has a lot of template to create projects with different setup easily (like choose which database you want or to bootstrap the frontend for you).
I also dwelled a bit on frontend development with &lt;a class="reference external" href="https://clojurescript.org/"&gt;ClojureScript&lt;/a&gt; (the version of Clojure designed to run in the browser) and &lt;a class="reference external" href="https://github.com/reagent-project/reagent"&gt;Reagent&lt;/a&gt;, a library written in ClojureScript that leverages React to create dynamic interfaces.
After some quick tests, I knew I would be able to do what I wanted.&lt;/p&gt;
&lt;p&gt;My goal was to create a site to help manage various competitions.
In the end, I mostly played with Reagent and coded the home page of the site as well as the detail pages for each competitions.
I initially wanted to add a form to add competitions and a search feature but gave up due to lack of time.
The frontend was entirely done in Reagent and communicated with the backend thanks to REST.
The code is available &lt;a class="reference external" href="https://gitlab.com/Jenselme/cljgolf"&gt;here&lt;/a&gt; if you want to take a look at it.&lt;/p&gt;
&lt;p&gt;I liked how the frontend and backend can work together and never require a full server restart.
The code, from both the frontend and the backend, is updated via hot updates which makes the developer experience very fluid.
That's also very important since the server is very slow to start, without this auto-reload feature, I think the development experience would be very poor.
It also means I almost never had to fully reload the page.
That was really pleasing.&lt;/p&gt;
&lt;p&gt;On the unpleasing side of things: I decided to use a SQL database (as I saw while making the getting started).
From what I tested, that's where the framework doesn't shine: I had to write SQL for each query I had to make which doesn't seem very flexible to me.
Maybe because I'm too used to the Django ORM by now to revert to raw SQL for every requests.
I also had to restart the server each time I edited a query for my changes to be taken into account.
That was adding to the pain I felt when writing SQL queries and one of the reason I decided not to take more time on the project: it would require even more queries.
I looked for ways to avoid this, but didn't find anything (I may not have looked hard enough) and still thinks the framework should handle this for me.
What's strange is other than that, the experience is very fluid and hot reload work perfectly.
This made me think that it's also partially why MongoDB is so popular: all you need is to send JSON to the server and all languages have good tools to do this, mostly Clojure with its emphasis on data and using maps &lt;a class="footnote-reference" href="#what-are-maps" id="footnote-reference-1"&gt;[1]&lt;/a&gt; everywhere.
No need for someone to build and maintain the complex piece of technology that is an ORM.&lt;/p&gt;
&lt;p&gt;All in all, I'm satisfied with what I did and intend to continue trying stuff with Clojure.
If I do something else, I'll probably use MongoDB to avoid SQL altogether.&lt;/p&gt;
&lt;table class="docutils footnote" frame="void" id="what-are-maps" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Also known as dict in Python.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content><category term="Blog"></category><category term="Clojure"></category><category term="Luminus"></category><category term="Reagent"></category></entry><entry><title>Retrospective on my biggest personal project</title><link href="https://www.jujens.eu/posts/en/2020/Jul/19/last-run-retrospective/" rel="alternate"></link><published>2020-07-19T00:00:00+02:00</published><updated>2020-07-19T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2020-07-19:/posts/en/2020/Jul/19/last-run-retrospective/</id><summary type="html">&lt;p class="first last"&gt;A retrospective on my biggest and longest personal project.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Today I'd like to look back at my longest (and biggest) personal project and what I learned along the way.
The project is now called &lt;em&gt;Last Run&lt;/em&gt; (but was named &lt;em&gt;Arena of Titans&lt;/em&gt; when we started).
It's a board game adapted for web usage.
It went live a few month ago and is available &lt;a class="reference external" href="https://www.last-run.com/"&gt;here&lt;/a&gt;.
The project is also entirely Open Source under the AGPLv3 license with the code source hosted on &lt;a class="reference external" href="https://gitlab.com/arenaoftitans/"&gt;gitlab&lt;/a&gt;.
If you play and have feedback, let me know!
We are on social media or you can leave a comment here.&lt;/p&gt;
&lt;p&gt;I started the project in 2014 when I was completing my studies in Centrale Marseille, a French engineering school.
At the time, we were 4 on the project: the creator of the game, myself and two other developers.
This project was accepted as our final project assignment which was a great way to combine passion and usefulness.
Now we are three: the creator of the game who does a bit of development but is mostly working on the gameplay, myself as the main developer and a new developer who participates from time to time.&lt;/p&gt;
&lt;p&gt;The original version of the game was written in Java and AngularJS.
I remember I looked at &lt;a class="reference external" href="https://wicket.apache.org/"&gt;Apache Wicket&lt;/a&gt; and &lt;a class="reference external" href="https://vaadin.com/"&gt;Vaadin&lt;/a&gt; to do everything in Java, including the frontend.
This idea was influenced by a fellow student (but not teammate) who really liked Vaadin (and maybe still does).
Eventually, after some tests, it became clear the project wasn't really fitting into this model (don't ask me exactly why, it was years ago) and I settled on AngularJS (for the best I think).
At this time, the frontend did HTTP requests to the API because it was easier this way given what we knew and all players had to play on the same computer because it was easier for us and we didn't really know how to update other browsers even if they were connected to the same API.
Since HTTP requests should be stateless, I stored the game state in Redis which is the only original technical decision that remained over the years.
It was also the early days of Docker, which looked cool and could allow me to avoid installing Java on my server, so I initially deployed the app with it.
It was a bit painful (mostly because I did it at the very end of the project so the examiners could see it on the web) but it was interesting and fun to do.&lt;/p&gt;
&lt;p&gt;After graduation, the creator of the game and myself still wanted to work on the game to improve it while the other members of the team didn't and left.
I tried to improve the Java version, mostly to add proper multiplayer and to rely on websocket to provide real time, two-way communication with the API.
However, I've always been a Python guy and choose Java for the first version of the game only because it was more convenient for other team members.
I was also a bit tired of the ES5/AngularJS frontend and wanted to switch to ES6 (we were in 2015 at this time, JS already made its &amp;quot;revolution&amp;quot; with ES6).
The fact that ES5/AngularJS was what I was using at work also influenced my decision: I wanted to play with something new and different.&lt;/p&gt;
&lt;p&gt;So, I decided to rewrite the thing.
I tested few options in the backend:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Go: it felt complicated given what I knew and what I was trying to achieve. Furthermore, I dislike the type system, maybe because I came from a Java and Python background. My main paint point was that I was already using sets in the algorithm and since Go doesn't have sets in the standard library, I needed an extra package. And since Go doesn't support generics, I had to allow any type in my sets which defeats the purpose of a type system in my opinion. That's the equivalent of doing &lt;tt class="docutils literal"&gt;Set&amp;lt;Object&amp;gt;&lt;/tt&gt; in Java which is strongly discouraged.&lt;/li&gt;
&lt;li&gt;NodeJS: it felt very young and was lacking some modern APIs (like &lt;tt class="docutils literal"&gt;Set&lt;/tt&gt;) at the time. That would have required to either not use them or to somehow transpile the code which I wasn't willing to do. And the future of the project wasn't very clear at the time with the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Node.js#Io.js"&gt;io.js&lt;/a&gt; fork.&lt;/li&gt;
&lt;li&gt;Python: I was able to do all I wanted and it's a language I always used and loved. I was also using it at work, so it made sense to use it here too. Did I really need to test anything else you may ask? For the sake of testing and experimenting I may answer. I choose (and still use today) the &lt;a class="reference external" href="https://autobahn.readthedocs.io/en/latest/"&gt;Autobahn library&lt;/a&gt; for websocket communication. What I found interesting was that I could use it with &lt;tt class="docutils literal"&gt;asyncio&lt;/tt&gt; (which landed in the standard library in Python 3.4, the Python version of the time) which seemed like the way to go for async programing. That was a very goo choice, mostly in Python 3.5 when the &lt;tt class="docutils literal"&gt;async&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;await&lt;/tt&gt; keywords were introduced and completely made &lt;tt class="docutils literal"&gt;asyncio&lt;/tt&gt; the way to go for new async code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the frontend, I tried several options (VueJS wasn't really a thing at this time, so I didn't tested it):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Angular 2 (now just named Angular): it seemed like the logical choice since the existing app was written in AngularJS. The framework was in Beta at the time. I really didn't like the experience it provided: it felt very complicated and cluttered. I also encounter several problems with the router which didn't invite me to trust the framework. Even after reading a book about it a few months later, after it was officially released, it still felt over-engineered and not that clear to me. So I was glad I discarded it.&lt;/li&gt;
&lt;li&gt;Ember: it felt nice but limiting because of how it handles data. I didn't really adhere to its ways of doing things. Still felt like a strong framework.&lt;/li&gt;
&lt;li&gt;React: it didn't left much of an impression at the time. I don't think it was that popular back then and it felt like I would lack some structure and need to add many things by myself (which is consistent with the fact it's more a rendering library than a framework).&lt;/li&gt;
&lt;li&gt;Aurelia: that's the framework I immediately loved. It felt like an updated and simplified (in a good sense) version of AngularJS. I saw how I could transfer and improve my existing practices of working with AngularJS. I still think it is an under-appreciated framework that deserves to be more known.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So after these tests, I decided to rewrite the project in Python with Autobahn using &lt;tt class="docutils literal"&gt;asyncio&lt;/tt&gt; and Aurelia in the frontend.
I started in July 2015 and I think I completed a first version during the rest of the summer.
Of course the project evolved over time:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;I added CI to run my tests automatically.&lt;/li&gt;
&lt;li&gt;I add many linters (both in the backend and in the frontend). I also added &lt;a class="reference external" href="https://prettier.io/"&gt;prettier&lt;/a&gt; and &lt;a class="reference external" href="https://black.readthedocs.io/en/stable/"&gt;black&lt;/a&gt; to automatically format my code. I check formatting with a pre-commit hook in git and even let PyCharm reformat the code on save.&lt;/li&gt;
&lt;li&gt;I switched to &lt;a class="reference external" href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt; to manage my pre-commit hooks.&lt;/li&gt;
&lt;li&gt;I switched to &lt;a class="reference external" href="https://aurelia.io/docs/plugins/store"&gt;Aurelia store&lt;/a&gt; to manage the application state. I initially relied on a custom service but relying on a more complete solution made sense in the end. See &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2019/Aug/24/aurelia-store/"&gt;this article&lt;/a&gt; to learn more about this process.&lt;/li&gt;
&lt;li&gt;I added validation in the API. Initially I always assumed I was given proper data (I guess I was young and unafraid back then). By the way, in Python, &lt;a class="reference external" href="https://docs.python-cerberus.org/en/stable/"&gt;Cerberus&lt;/a&gt; is a very good library to do that!&lt;/li&gt;
&lt;li&gt;I'm stricter in my usage of functions: initially I embraced the flexibility of Python a bit to much and allowed both &lt;tt class="docutils literal"&gt;Card&lt;/tt&gt; object and tuple &lt;tt class="docutils literal"&gt;(card_name, card_color)&lt;/tt&gt; (which allowed me to find the proper &lt;tt class="docutils literal"&gt;Card&lt;/tt&gt; object) to be passed around which made the code more complex and more fragile. I'm stricter now in how I define the interfaces of my functions.&lt;/li&gt;
&lt;li&gt;I reduce various subjects of tech debts and simplified many portions of the code thanks to all the things I learned in Python, JS and programming in general over the last 6 years.&lt;/li&gt;
&lt;li&gt;I changed a few times of package managers and bundles for the frontend. See &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2020/Jul/04/aurelia-wepack/"&gt;this article&lt;/a&gt; to learn more.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One very important point I haven't talked about yet is the deploy process.
That's another area where I learned much over these years!
I had a server even as a student, so I used it even for the earliest versions of the game.
I don't remember exactly how I deployed the first version of the app using docker though.
I think I just exported the image as a &lt;tt class="docutils literal"&gt;tar.gz&lt;/tt&gt; and imported it on the server with some kind of custom Bash script.
Since it was the early days of docker which wasn't very stable at the time, I switched to standard &lt;a class="reference external" href="https://uwsgi-docs.readthedocs.io/en/latest/"&gt;uwsgi&lt;/a&gt; directly on the server and packaging all Python dependencies as RPM packages installed on the system (luckily I didn't have much of those because it takes a lot of time!).
Later, when I wanted to switch to a more powerful server, I think I switched back to docker because it made dependency management and deploys easier.
At this stage, I was still using custom Bash scripts to build and deploy images.
The front was deployed with Rsync.
It was a bit weird, not always stable but globally worked.
After that, I switched jobs and discovered &lt;a class="reference external" href="https://docs.ansible.com/ansible/latest/"&gt;Ansible&lt;/a&gt; as a tool to deploy stuff.
Since it way more stable and complete than what I could create by hand, I switched to it a few years back.
That's where I am now: a bunch of Ansible playbooks to build and deploy both the frontend and the backend.
The backend runs in docker and the frontend is served directly by nginx because it doesn't need anything else.
I still have a Redis instance that was deployed once, so it doesn't bother me much.&lt;/p&gt;
&lt;p&gt;The last thing I'd like to share is how I handle multiple versions of the game.
It's not very useful right now since we don't have many players but it felt like something I had to do correctly from the start because it would be harder to add later on.
Since the game can change from one version to another (because we had features), incompatibilities can arise: the frontend can send a request in an older format or the API can require new information the frontend cannot send yet.
The result would be a broken game for the player.
I could use feature flags to detect which code path to use and have a single instance of the API but I think it would clutter the code more than serving the actual purpose.
So I decided that each version of the frontend would talk to a dedicated version of the backend.
To do that, I developed a small gateway which reads the version of the API the frontend wants and transfer the traffic to the proper container.
It's written in Go because I really wanted to give another try to this language and though this routing gateway would be a very good fit for the language (which I still think it is).
When you go on the site, nginx will always serve the latest version of the frontend by default.
Once you start a game, the version of the frontend is used in the URL so if you refresh the page, you will get the proper version.&lt;/p&gt;
&lt;p&gt;To conclude, I really like the project.
It took time to go to where we are now for various reasons: I made mistakes because I was a beginner, I can only work on the project during my spare time, I'm mostly the only developer working on the project and we made some changes in the gameplay that had a huge impact on the structure of the project.
Overall, I am pleased and proud of the work accomplished.
I learned a lot, was able to try thing and spent countless hours over Python, JS, Bash or Docker problems.
This may sound like a loss of time but it was a great way to learn.
I still plan to work on this project and don't know where it will be in 6 years from now.
If it's still around, I'll try to publish a follow up article!&lt;/p&gt;
</content><category term="Blog"></category></entry><entry><title>Feedback after switching to openSUSE Tumbleweed</title><link href="https://www.jujens.eu/posts/en/2020/May/15/feedback-after-switching-tumbleweed/" rel="alternate"></link><published>2020-05-15T00:00:00+02:00</published><updated>2020-05-15T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2020-05-15:/posts/en/2020/May/15/feedback-after-switching-tumbleweed/</id><summary type="html">&lt;p&gt;This article is a follow up to &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2020/Feb/23/opensuse-install-btrfs-subvolumes/"&gt;Installing openSUSE next to Fedora with BTRFS&lt;/a&gt; where I detailed how I switched to openSUSE Tumbleweed.
In this article, now that I have been using Tumbleweed for about 2 months and a half, I'll give some feedback on my experience.&lt;/p&gt;
&lt;p&gt;The issues I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This article is a follow up to &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2020/Feb/23/opensuse-install-btrfs-subvolumes/"&gt;Installing openSUSE next to Fedora with BTRFS&lt;/a&gt; where I detailed how I switched to openSUSE Tumbleweed.
In this article, now that I have been using Tumbleweed for about 2 months and a half, I'll give some feedback on my experience.&lt;/p&gt;
&lt;p&gt;The issues I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;For some reason, I lost an EFI file used by Fedora to boot which prevented me to run my Fedora installation. I was able to restore it by copying another EFI file (&lt;tt class="docutils literal"&gt;mmx.efi&lt;/tt&gt;) into the proper location.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Unlocking the LUCKS drive in Grub is slow. Although I find it's a bit faster lately (and it's not an issue with Tumbleweed per say).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Docker sometimes fails to pull images. It only happens in script, running &lt;tt class="docutils literal"&gt;docker pull SOME_IMAGE&lt;/tt&gt; always runs correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Unlike in Fedora, &lt;tt class="docutils literal"&gt;/tmp&lt;/tt&gt; is not cleaned automatically after a reboot. You have to enable this manually by putting the content below in &lt;tt class="docutils literal"&gt;/etc/tmpfiles.d/tmp.conf&lt;/tt&gt; (see &lt;a class="reference external" href="https://forums.opensuse.org/showthread.php/535050-tmp-amp-var-tmp-safe-to-remove?p=2895079#post2895079"&gt;this thread&lt;/a&gt; for the solution and the reasoning behind it):&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="code aconf literal-block"&gt;
&lt;span class="c"&gt;#  This file is part of systemd.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#  systemd is free software; you can redistribute it and/or modify it&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#  under the terms of the GNU Lesser General Public License as published by&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#  the Free Software Foundation; either version 2.1 of the License, or&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#  (at your option) any later version.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# See tmpfiles.d(5) for details&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Clear tmp directories separately, to make them easier to override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# SUSE policy: we don't clean those directories&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;D&lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;/tmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1777&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;/var/tmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1777&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;-
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Updating the kernel to a new minor version (eg from 4.5 to 4.6) can break the Nvidia drivers and thus your display. The patched driver comes relatively fast but it was a bad surprise when it happened to me! I hope the process will be smother in the future. It was also the occasion to experiment booting from a read-only BTRFS snapshot. And it worked well!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite these issues (for which I have workarounds anyway), I am very pleased with my experience.
The update process works well (with the exception of Nvidia drivers as pointed above) and the system is stable.
So I decided to switch all my computers to it (I switched to a rolling release to avoid Fedora updates after all, didn't I?).&lt;/p&gt;
&lt;p&gt;I must also say that YAST (which acts as a configuration panel for you computer from firewall to installing packages) is well done and a pleasure to use.
No need to search/install different tools, just use YAST!&lt;/p&gt;
&lt;p&gt;I also find the use of openSUSE of BTRFS snapshots to be really cool.
I haven't used them much since the system is stable and I haven't encountered issues with updates (except once with Nvidia).
But it's still a nice thing.
I'll just note that while installing openSUSE to another computer, I made a big configuration mistake that locked me out of the system.
Thanks to the snapshots and the rollback mechanism, it wasn't such a big deal (because yes, &lt;tt class="docutils literal"&gt;/etc&lt;/tt&gt; will be in the snapshot too).
Without it, I might have needed to reinstall everything.
So I don't need them often, but I'm really glad they are here when I do.&lt;/p&gt;
&lt;p&gt;This post wouldn't be complete without some observation on KDE.
As a long time Gnome user, I also decided to switch to KDE to see how it goes (and while your changing, why not change more?).
I am pleased with the change and was able to adapt to KDE very quickly (despite many years of using Gnome).
I do appreciate the fact that many things are integrated by default (like the clipboard manager or media player integration) whereas you need extension in Gnome to do this.
I also really like the fact that the command that ask for the root password is mentioned on the popup that ask for it.
I think it is a big plus for security.&lt;/p&gt;
&lt;p&gt;I had some issues though, here is the list and how I solved them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;I cannot configure my gmail account to use it easily with KMail. From what I read, I have to enable IMAP support in gmail. In Gnome this is supported by default (calendar works fine though). Since I only have my professional mailbox in it, I can just keep gmail opened in a pined tab. So not a big deal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Since I use the same user on both Fedora and openSUSE, when KDE launched it changed some configuration and now the close, maximise and minimise icons looks a bit funny on Gnome.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;I use the &lt;a class="reference external" href="http://bepo.fr/wiki/Accueil"&gt;bépo&lt;/a&gt; keyboard disposition (which was made specifically for French). It was really easy to configure this keyboard disposition in the session but harder to enable it on the login screen (although it may come from the fact that openSUSE doesn't support this disposition during install unlike Fedora). Luckily, &lt;a class="reference external" href="https://forums.archlinux.fr/viewtopic.php?t=19684"&gt;this post on the ArchLinux forum&lt;/a&gt; had the solution: update &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/etc/X11/xorg.conf.d/00-keyboard.conf&lt;/span&gt;&lt;/tt&gt; with the content below.&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="code aconf literal-block"&gt;
&lt;span class="nb"&gt;Section&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;InputClass&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;Identifier&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Keyboard Layout&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;MatchIsKeyboard&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;yes&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;MatchDevicePath&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/input/event*&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;XkbLayout&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fr&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;XkbVariant&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bepo_latin9&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;XkbOptions&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;terminate:ctrl_alt_bksp&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;EndSection&lt;/span&gt;
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;I had to create a script in &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;.config/autostart-scripts/ssh-add.sh&lt;/span&gt;&lt;/tt&gt; with the content below to correctly pre-load my SSH keys, something that worked by default on Gnome. The script is based on &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/KDE_Wallet#Using_the_KDE_Wallet_to_store_ssh_key_passphrases"&gt;this article from the ArchLinux wiki&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="ch"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;key&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;~/.ssh/*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;pub&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;authorized_keys&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;known_hosts&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;seahorse&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;ssh-add&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also find the combo KDE + openSUSE to need less RAM that my Fedora + Gnome.&lt;/p&gt;
&lt;p&gt;So, to conclude, I am very pleased with the change and don't regret doing it.&lt;/p&gt;
</content><category term="Blog"></category><category term="Linux"></category></entry><entry><title>Installing openSUSE next to Fedora with BTRFS</title><link href="https://www.jujens.eu/posts/en/2020/Feb/23/opensuse-install-btrfs-subvolumes/" rel="alternate"></link><published>2020-02-23T00:00:00+01:00</published><updated>2020-02-23T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2020-02-23:/posts/en/2020/Feb/23/opensuse-install-btrfs-subvolumes/</id><summary type="html">&lt;p&gt;Update: &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2020/May/15/feedback-after-switching-tumbleweed/"&gt;My feedback article&lt;/a&gt; is now available.&lt;/p&gt;
&lt;p&gt;I wanted to install openSUSE Tumbleweed (the rolling release version of openSUSE) on one of my computers to see how it looked outside a VM (and thus to try to use it daily).
I am thinking about switching to this distribution to avoid …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Update: &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2020/May/15/feedback-after-switching-tumbleweed/"&gt;My feedback article&lt;/a&gt; is now available.&lt;/p&gt;
&lt;p&gt;I wanted to install openSUSE Tumbleweed (the rolling release version of openSUSE) on one of my computers to see how it looked outside a VM (and thus to try to use it daily).
I am thinking about switching to this distribution to avoid major updates of Fedora.
They happen every 6 months and I have multiple machines to update, so I find it cumbersome to update them all (and I like to test new stuff too ^^).&lt;/p&gt;
&lt;p&gt;Why openSUSE? Because it looks stable, well tested, it provides a graphical installer (I already installed Arch and Gentoo in the past and wasn't enthusiastic about doing this again) and has some nice defaut with BTRFS (snapshots and booting from snapshots without having to configure it manually).
I also know a contributor and a long time user of openSUSE and Tumbleweed who's very happy about the system.&lt;/p&gt;
&lt;p&gt;In this article, I'll explain how I installed it next to my Fedora (which I don't want to loose it until I commit to making the switch).
Some things are specific to my setup, others are more generic and should be reusable (more or less easily).
If you have any question or remark, please leave a comment.
I'll assume you have some knowledge of BTRFS.&lt;/p&gt;
&lt;div class="section" id="starting-point"&gt;
&lt;h2&gt;Starting point&lt;/h2&gt;
&lt;p&gt;I had fedora installed with a BTRFS top level volume and 3 child volumes &lt;tt class="docutils literal"&gt;root&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;/&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;var&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;/var&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;home&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;/home&lt;/tt&gt;.
Since I use docker with the BTRFS driver I also had many subvolumes of &lt;tt class="docutils literal"&gt;/var&lt;/tt&gt; for docker.&lt;/p&gt;
&lt;p&gt;I also installed Tumbleweed in a VM to see how it behaves.
I created another VM with Fedora installed just like on my computer so I could try installing Tumbleweed next to it without breaking my current setup.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="preparing-fedora"&gt;
&lt;h2&gt;Preparing Fedora&lt;/h2&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;I removed the docker containers and images as well as all docker related subvolumes that remained. I don't know whether this could have an impact and didn't want to risk it (nor did I wish to migrate them).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;The two most useful commands you can use at anytime to check something are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;btrfs subvolume list PATH&lt;/tt&gt; to list all subvolumes in the supplied filesystem.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;btrfs subvolume show PATH&lt;/tt&gt; to see which subvolume is mounted under the supplied path.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Disable SELinux: it's not used in openSUSE so I think some files will be mislabeled which may prevent Fedora to work properly (or maybe just prevent me to access some files, I don't know and I don't want to test). Let's simply as much as possible and remove it from the equation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Since my subvolumes are named &lt;tt class="docutils literal"&gt;var&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;root&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;home&lt;/tt&gt;, the Tumbleweed installer will mount them under &lt;tt class="docutils literal"&gt;/var&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;/root&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;/home&lt;/tt&gt;. I didn't find a way to prevent this. For &lt;tt class="docutils literal"&gt;/home&lt;/tt&gt; it's the behavior I&amp;nbsp;want since I want to keep my data. For the other two, it's not. You cannot rename subvolumes directly but you can rely on snapshots to achieve it. To do so, I (as root):&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Mounted the top level BTRFS volume (which is not accessible by default) with: &lt;tt class="docutils literal"&gt;mount &lt;span class="pre"&gt;-t&lt;/span&gt; btrfs &lt;span class="pre"&gt;-o&lt;/span&gt; subvolid=5 /dev/vda3 /mnt/toplevel/&lt;/tt&gt; (most of my commands are copied from when I tested in a VM, hence the &lt;tt class="docutils literal"&gt;vda&lt;/tt&gt;, adapt this part to your setup).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Created a snapshot for the &lt;tt class="docutils literal"&gt;root&lt;/tt&gt; subvolume: &lt;tt class="docutils literal"&gt;btrfs subvolume snapshot /mnt/toplevel/root/ /mnt/toplevel/fedoraroot/&lt;/tt&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Emptied &lt;tt class="docutils literal"&gt;/mnt/toplevel/fedoraroot/var&lt;/tt&gt; to remove the directory with &lt;tt class="docutils literal"&gt;rmdir /mnt/toplevel/fedoraroot/var&lt;/tt&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Created another snapshot for &lt;tt class="docutils literal"&gt;var&lt;/tt&gt; (I decided to make it a subvolume of &lt;tt class="docutils literal"&gt;fedoraroot&lt;/tt&gt; because I think it makes sense to have one top volume for Fedora): &lt;tt class="docutils literal"&gt;btrfs subvolume snapshot /mnt/toplevel/var/ /mnt/toplevel/fedoraroot/&lt;/tt&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Migrated the nested subvolumes for &lt;tt class="docutils literal"&gt;/var&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;baley toplevel]# rmdir /mnt/toplevel/fedoraroot/var/lib/machines/
[root&amp;#64;baley toplevel]# btrfs subvolume snapshot /mnt/toplevel/var/lib/machines/ /mnt/toplevel/fedoraroot/var/lib/
[root&amp;#64;baley toplevel]# rmdir /mnt/toplevel/fedoraroot/var/lib/portables/
[root&amp;#64;baley toplevel]# btrfs subvolume snapshot /mnt/toplevel/var/lib/portables/ /mnt/toplevel/fedoraroot/var/lib/
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Identified each subvolume so I could know which ones where mounted as my main filesystem:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;localhost ~]# touch /mnt/toplevel/root/stdroot
[root&amp;#64;localhost ~]# touch /mnt/toplevel/fedoraroot/fedoraroot
[root&amp;#64;baley toplevel]# touch fedoraroot/var/fedoravar
[root&amp;#64;baley toplevel]# touch ./var/stdvar
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Updated fstab &lt;em&gt;in the snapshot&lt;/em&gt; (that's in &lt;tt class="docutils literal"&gt;fedoraroot&lt;/tt&gt;) since that what we want to use from now on. To do so, I replaced &lt;tt class="docutils literal"&gt;subvol=root&lt;/tt&gt; by &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;subvol=fedoraroot```and&lt;/span&gt; ``subvol=var&lt;/tt&gt; by &lt;tt class="docutils literal"&gt;subvol=fedoraroot/var&lt;/tt&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Rebooted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Edited the grub line from the menu before booting from it. I updated the &lt;tt class="docutils literal"&gt;subvol&lt;/tt&gt; parameter of the kernel to be &lt;tt class="docutils literal"&gt;subvol=fedoraroot&lt;/tt&gt;. This way grub mounted the proper subvolume during boot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Checked that everything was correct:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;localhost ~]# btrfs subvolume show /
fedoraroot
        Name:                   fedoraroot
        UUID:                   1fc8ffa2-76f5-a343-aae9-4b499073d805
        Parent UUID:            5effda61-179e-9b48-900f-b7ad85d5f9d9
        Received UUID:          -
        Creation time:          2020-02-22 16:14:19 +0100
        Subvolume ID:           268
        Generation:             4554
        Gen at creation:        4447
        Parent ID:              5
        Top level ID:           5
        Flags:                  -
        Snapshot(s):
[root&amp;#64;localhost ~]# ls /fedoraroot
/fedoraroot
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Remounted the top level volume.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Deleted the nested subvolumes: &lt;tt class="docutils literal"&gt;btrfs subvolume delete /mnt/toplevel/root/var/lib/machines/&lt;/tt&gt; (same for portables).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Deleted &lt;tt class="docutils literal"&gt;root&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;var&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
btrfs subvolume delete /mnt/toplevel/root/
btrfs subvolume delete /mnt/toplevel/var/
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Updated grub: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;grub2-mkconfig&lt;/span&gt; &lt;span class="pre"&gt;-o&lt;/span&gt; /boot/grub2/grub.cfg&amp;nbsp;&amp;nbsp; # or /boot/efi/EFI/fedora/grub.cfg if you use EFI&lt;/tt&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Checked the generated grub configuration:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;localhost ~]# grep -R subvol /boot/grub2/grub.cfg  # The subvolume parameter is correct.
set default_kernelopts=&amp;quot;root=UUID=ce254a08-22e0-481e-b027-498aa059cc1a ro rootflags=subvol=fedoraroot resume=UUID=163891ab-ac20-4e58-8731-306810f99c9c rhgb quiet &amp;quot;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Rebooted. All should still be working.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-tumbleweed"&gt;
&lt;h2&gt;Installing Tumbleweed&lt;/h2&gt;
&lt;p&gt;Just do the normal installation process until you reach the disk setup. Here you'll have to choose &lt;em&gt;Expert partition&lt;/em&gt; and &lt;em&gt;Start with existing partitions&lt;/em&gt;. Then, on the disk that contains you BTRFS volume:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Mount the &lt;tt class="docutils literal"&gt;swap&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Mount the EFI partition under &lt;tt class="docutils literal"&gt;/boot/efi&lt;/tt&gt; (if you use EFI).&lt;/li&gt;
&lt;li&gt;Mount the main BTRFS volume &lt;tt class="docutils literal"&gt;/&lt;/tt&gt; and be sure to skip the formatting of the volume. &lt;tt class="docutils literal"&gt;/boot&lt;/tt&gt; must be under &lt;tt class="docutils literal"&gt;/&lt;/tt&gt; in the BTRFS volume even if you use luks. See below for why.&lt;/li&gt;
&lt;li&gt;Check installation summary: should not remove anything but should create a lot of new subvolume. None should conflict with fedora's.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That should be it. Inspect in the summary the operations done.
You should see that the installer:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Will create all the subvolumes it needs.&lt;/li&gt;
&lt;li&gt;Won't delete anything.&lt;/li&gt;
&lt;li&gt;Will mount the fedora subvolumes. Not a big deal, we will remove that from the fstab once the installation is done.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can launch the installation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="finishing-the-setup-of-tumbleweed"&gt;
&lt;h2&gt;Finishing the setup of Tumbleweed&lt;/h2&gt;
&lt;p&gt;If you use luks, you will have to enter you luks passphrase in Grub with a US keyboard.
You will then have to enter you passphrase normally as part of the boot process.&lt;/p&gt;
&lt;p&gt;By default Tumbleweed will create a subvolume named &lt;tt class="docutils literal"&gt;&amp;#64;&lt;/tt&gt; and create its other subvolumes as subvolumes of it.
Here however, it didn't (I don't know why).
Since I still want a main subvolume for each system (having &lt;tt class="docutils literal"&gt;home&lt;/tt&gt; as a direct subvolume of the top level volume sounds good to me since it is independent of any other system).
So, it's time to move subvolumes again. I won't detail much this time, I think you know the drill (do this from fedora, the directories are in use in Tumbleweed):&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
mount&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subvolid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/vda3&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel/&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel/&lt;span class="w"&gt;
&lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Repeat for each subvolume.
&lt;/span&gt;rmdir&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel/&amp;#64;/var/&lt;span class="w"&gt;
&lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;snapshot&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel/var&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel/&amp;#64;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Check. Don't remove subvolume while in use, the deletion will come later.
&lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel&lt;span class="w"&gt;
&lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/pre&gt;
&lt;p&gt;Let's edit the fstab.
We are just appending &lt;tt class="docutils literal"&gt;&amp;#64;/&lt;/tt&gt; to each &lt;tt class="docutils literal"&gt;subvol&lt;/tt&gt; arguments like this: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;subvol=&amp;#64;/var&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;We also need to update &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/boot/grub2/x86_64-efi/load.cfg&lt;/span&gt;&lt;/tt&gt; so grub mounts the correct subvolume during the boot process:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
set btrfs_relative_path='y'
cryptomount -u bf63369674824969a7726fda0cb46537
btrfs-mount-subvol ($root) /boot/grub2/x86_64-efi &amp;#64;/boot/grub2/x86_64-efi
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I didn't need this step in my VM and given the content of the file, I think it is specific to luks.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We can now reboot. Everything should go well. Boot on the other system if not.&lt;/p&gt;
&lt;p&gt;We can now delete the old subvolumes from the installation:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
mount&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subvolid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/vda3&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel/&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/mnt/toplevel/&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Repeat for each subvolume.
&lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;delete&lt;span class="w"&gt; &lt;/span&gt;./var
&lt;/pre&gt;
&lt;p&gt;We can also enable snapper now to have snapshot.
It's done automatically during a normal installation but not here (because we did weird things I guess).
We can do this with &lt;tt class="docutils literal"&gt;snapper &lt;span class="pre"&gt;-c&lt;/span&gt; root &lt;span class="pre"&gt;create-config&lt;/span&gt; /&lt;/tt&gt;.
And we are done!&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;p class="last"&gt;Fedora uses subvol in &lt;tt class="docutils literal"&gt;grub.cfg&lt;/tt&gt; but not Tumbleweed. Tumbleweed changes the default subvolume of the top level volume. It's not a problem with this setup but if you want to install two systems that do this, you will have to make at least one explicitly use &lt;tt class="docutils literal"&gt;subvol&lt;/tt&gt; everywhere.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="wrapping-up"&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It required a lot of preparation and tests but I'm glad I made it.
I find Tumbleweed fast (faster than fedora at boot and when installing package) and reliable (from what I used until now).
I'll need to do more testing to confirm that (the system is very fresh after all).
I also learned a lot about BTRFS, subvolumes and snapshots along the way.
I still have some issues though:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The nextcloud client ask me to reconnect each time I launch it. It may come from a configuration shared with Fedora. I'll fix this later once I know which system I want to keep.&lt;/li&gt;
&lt;li&gt;The grub menu to choose the kernel (or the snapshot) is sometimes not displayed.&lt;/li&gt;
&lt;li&gt;Grub takes some time to start once the key is entered.&lt;/li&gt;
&lt;li&gt;If I fail to enter my passphrase for grub of the first try, it fails and I have to do a hard reboot.&lt;/li&gt;
&lt;li&gt;I decided to switch from Gnome to KDE and I think I hit a bug with the online accounts that prevent me to setup my gmail account. I'll have to dig into this later (I'll try to update this post if I find the solution). I also spent a lot of time to configure KMail (which has a lot of options by the way).&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="bonus"&gt;
&lt;h2&gt;Bonus&lt;/h2&gt;
&lt;div class="section" id="enter-luks-passphrase-only-once"&gt;
&lt;h3&gt;Enter luks passphrase only once&lt;/h3&gt;
&lt;p&gt;If you use luks, you have to enter your luks passphrase twice: once to unlock &lt;tt class="docutils literal"&gt;/boot&lt;/tt&gt; so grub can start the system and then so the system can access the disk.
Sadly, they can't communicate (yet?).&lt;/p&gt;
&lt;p&gt;You could put &lt;tt class="docutils literal"&gt;/boot&lt;/tt&gt; on an unencrypted filesystem (that's what fedora does), but according to &lt;a class="reference external" href="https://forums.opensuse.org/showthread.php/525491-GRUB2-decryption-password-enter-twice"&gt;this thread&lt;/a&gt; you won't be able to boot from a BTRFS snapshot (which is one cool thing Tumbleweed enables by default).&lt;/p&gt;
&lt;p&gt;You can also fellow &lt;a class="reference external" href="https://cryptsetup-team.pages.debian.net/cryptsetup/encrypted-boot.html"&gt;these steps&lt;/a&gt; so you'll only have to enter the passphrase for grub. Check also &lt;a class="reference external" href="https://forums.opensuse.org/showthread.php/525314-How-can-I-insert-a-file-into-the-initramfs"&gt;this link&lt;/a&gt; to update the initramfs in Tumbleweed (don't forget to launch &lt;tt class="docutils literal"&gt;dracut &lt;span class="pre"&gt;--force&lt;/span&gt;&lt;/tt&gt;). For reference, my &lt;tt class="docutils literal"&gt;/etc/dracut.conf&lt;/tt&gt; contains:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# PUT YOUR CONFIG IN separate files
# in /etc/dracut.conf.d named &amp;quot;&amp;lt;name&amp;gt;.conf&amp;quot;
# SEE man dracut.conf(5) for options
install_items+=&amp;quot;/etc/luks-keys/root.key&amp;quot;
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;If you need to add many files, separate them with spaces like this: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;install_items+=&amp;quot;/etc/luks-keys/root.key&lt;/span&gt; &lt;span class="pre"&gt;/etc/luks-keys/swap.key&amp;quot;&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="first admonition-title"&gt;Tip&lt;/p&gt;
&lt;p class="last"&gt;Don't add &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;key-slot=1&lt;/span&gt;&lt;/tt&gt; immediately, if you did a mistake you will be locked out of the system because luks will try to match the key against this slot. Don't forget to adapt the slot if needed. You may also choose not to add it since I found it doesn't really change the boot time anyway.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tumbleweed-default-install"&gt;
&lt;h3&gt;Tumbleweed default install&lt;/h3&gt;
&lt;p&gt;For reference:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
localhost:~ # btrfs subvolume list /
ID 256 gen 32 top level 5 path &amp;#64;
ID 258 gen 4192 top level 256 path &amp;#64;/var
ID 259 gen 4125 top level 256 path &amp;#64;/usr/local
ID 260 gen 4192 top level 256 path &amp;#64;/tmp
ID 261 gen 3045 top level 256 path &amp;#64;/srv
ID 262 gen 4166 top level 256 path &amp;#64;/root
ID 263 gen 3620 top level 256 path &amp;#64;/opt
ID 264 gen 4192 top level 256 path &amp;#64;/home
ID 265 gen 26 top level 256 path &amp;#64;/boot/grub2/x86_64-efi
ID 266 gen 3550 top level 256 path &amp;#64;/boot/grub2/i386-pc
ID 267 gen 4118 top level 256 path &amp;#64;/.snapshots
ID 268 gen 4191 top level 267 path &amp;#64;/.snapshots/1/snapshot
ID 274 gen 197 top level 267 path &amp;#64;/.snapshots/2/snapshot
ID 275 gen 262 top level 267 path &amp;#64;/.snapshots/3/snapshot
ID 278 gen 485 top level 267 path &amp;#64;/.snapshots/4/snapshot
ID 282 gen 1614 top level 267 path &amp;#64;/.snapshots/5/snapshot
ID 283 gen 2100 top level 267 path &amp;#64;/.snapshots/6/snapshot
ID 290 gen 3024 top level 267 path &amp;#64;/.snapshots/11/snapshot
ID 291 gen 3066 top level 267 path &amp;#64;/.snapshots/12/snapshot
ID 292 gen 3233 top level 267 path &amp;#64;/.snapshots/13/snapshot
ID 295 gen 3551 top level 267 path &amp;#64;/.snapshots/14/snapshot
ID 296 gen 3593 top level 267 path &amp;#64;/.snapshots/15/snapshot
ID 297 gen 3613 top level 267 path &amp;#64;/.snapshots/16/snapshot
ID 301 gen 3631 top level 267 path &amp;#64;/.snapshots/17/snapshot
ID 305 gen 4005 top level 267 path &amp;#64;/.snapshots/18/snapshot
ID 306 gen 4051 top level 267 path &amp;#64;/.snapshots/19/snapshot
ID 307 gen 4113 top level 267 path &amp;#64;/.snapshots/20/snapshot

localhost:/mnt/toplevel # cat /etc/fstab
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /                       btrfs  defaults                      0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /var                    btrfs  subvol=/&amp;#64;/var                 0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /usr/local              btrfs  subvol=/&amp;#64;/usr/local           0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /tmp                    btrfs  subvol=/&amp;#64;/tmp                 0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /srv                    btrfs  subvol=/&amp;#64;/srv                 0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /root                   btrfs  subvol=/&amp;#64;/root                0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /opt                    btrfs  subvol=/&amp;#64;/opt                 0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /home                   btrfs  subvol=/&amp;#64;/home                0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /boot/grub2/x86_64-efi  btrfs  subvol=/&amp;#64;/boot/grub2/x86_64-efi  0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /boot/grub2/i386-pc     btrfs  subvol=/&amp;#64;/boot/grub2/i386-pc  0  0
UUID=e5a3ef21-d865-4bb6-bd11-6056ff4a60b1  /.snapshots             btrfs  subvol=/&amp;#64;/.snapshots          0  0
UUID=6f2fce23-6460-4d69-9e69-a5b6b5e4456c  swap                    swap   defaults                      0  0
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="Linux"></category><category term="BTRFS"></category></entry><entry><title>Mon passage à LineageOS</title><link href="https://www.jujens.eu/posts/2017/Mar/29/switch-to-lineageos/" rel="alternate"></link><published>2017-03-29T00:00:00+02:00</published><updated>2017-03-29T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2017-03-29:/posts/2017/Mar/29/switch-to-lineageos/</id><summary type="html">&lt;p&gt;Tout d'abord, je présente un projet intéressant : &lt;a class="reference external" href="https://f-droid.org/repository/browse/?fdfilter=Oandbackup&amp;amp;fdid=dk.jens.backup"&gt;oandbackup&lt;/a&gt; que j'ai découvert via &lt;a class="reference external" href="https://tuxicoman.jesuislibre.net/2017/02/passage-de-cyanogen-a-lineageos.html?pk_campaign=feed&amp;amp;amp;pk_kwd=passage-de-cyanogen-a-lineageos"&gt;cet article&lt;/a&gt;. oandbanck vous permet de sauvegarder les APK installés sur votre téléphone pour pouvoir les réinstaller une fois la mise à jour effectuée. Malheureusement, suite à un problème lors de la mise à jour, j'ai dû formater …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Tout d'abord, je présente un projet intéressant : &lt;a class="reference external" href="https://f-droid.org/repository/browse/?fdfilter=Oandbackup&amp;amp;fdid=dk.jens.backup"&gt;oandbackup&lt;/a&gt; que j'ai découvert via &lt;a class="reference external" href="https://tuxicoman.jesuislibre.net/2017/02/passage-de-cyanogen-a-lineageos.html?pk_campaign=feed&amp;amp;amp;pk_kwd=passage-de-cyanogen-a-lineageos"&gt;cet article&lt;/a&gt;. oandbanck vous permet de sauvegarder les APK installés sur votre téléphone pour pouvoir les réinstaller une fois la mise à jour effectuée. Malheureusement, suite à un problème lors de la mise à jour, j'ai dû formater ma carte SD, du coup, je n'ai pu tester que la sauvegarde et pas la restauration. Mais d'après ce que j'ai lu, ça fonctionne très bien.&lt;/p&gt;
&lt;p&gt;Concernant l'installation à proprement parler : j'avais déjà tenté une installation début février avec une nightly pour mon galaxy s2. Ça c'était mal passé : l'installation n'arrivait pas à se terminer. Après plusieurs tentatives infructueuses, les builds de cyanogenmod n'étant plus disponibles, j'ai fini par installer une vieille build de replicant pour pouvoir passer des appels et recevoir des SMS. Comme le projet lineageos a pas mal bougé (et que la build de replicant était vraiment vieille), j'ai retenté le week-end dernier une installation. J'ai eu quelques soucis mais j'ai réussi à les contourner. Voici ce que j'ai fait :&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Télécharger une image récente : &lt;a class="reference external" href="https://download.lineageos.org/"&gt;https://download.lineageos.org/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Télécharger l'utilitaire SU : &lt;a class="reference external" href="https://download.lineageos.org/extras"&gt;https://download.lineageos.org/extras&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Copier les fichiers sur la carte SD&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Installer TWRP (le mode recovery) : c'est à partir de là que les choses ont commencés à ce gâter. J'ai tenté la commande issue de la documentation:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
heimdall&lt;span class="w"&gt; &lt;/span&gt;flash&lt;span class="w"&gt; &lt;/span&gt;--KERNEL&lt;span class="w"&gt; &lt;/span&gt;twrp-3.1.0-0-i9100.img&lt;span class="w"&gt; &lt;/span&gt;--no-reboot
&lt;/pre&gt;
&lt;p&gt;Malheureusement, impossible de démarrer sous TWRP : le téléphone démarrait sous l''ancien recovery. J'ai bien tenté &lt;tt class="docutils literal"&gt;heimdall flash &lt;span class="pre"&gt;--KERNEL&lt;/span&gt; &lt;span class="pre"&gt;twrp-3.1.0-0-i9100.img&lt;/span&gt; &lt;span class="pre"&gt;--no-reboot&lt;/span&gt;&lt;/tt&gt; (ce que je faisais avec mes anciennes recovery) mais là, impossible de démarrer. D'après ce que j'ai compris, cela vient du fait que TWRP est plus qu'un simple mode recovery, il a besoin d'un noyau pour démarrer. La solution, c'est d'utiliser le noyau contenu dans l'image LineageOS pour pouvoir démarrer en mode recovery. Pour cela :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Dézipper &lt;tt class="docutils literal"&gt;boot.img&lt;/tt&gt; de l'image.&lt;/li&gt;
&lt;li&gt;Lancer avec le téléphone en mode &lt;em&gt;téléchargement&lt;/em&gt; :&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code bash literal-block"&gt;
heimdall&lt;span class="w"&gt; &lt;/span&gt;flash&lt;span class="w"&gt; &lt;/span&gt;--KERNEL&lt;span class="w"&gt; &lt;/span&gt;boot.img&lt;span class="w"&gt; &lt;/span&gt;--RECOVERY&lt;span class="w"&gt; &lt;/span&gt;twrp-3.1.0-0-i9100.img
&lt;/pre&gt;
&lt;p&gt;Normalement, maintenant, le téléphone peut démarrer en mode recovery.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Effacer les &amp;quot;caches&amp;quot; dalvik, data, system, et cache.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Lancer l'installation du zip. Dans mon cas, l'installation se bloquait aux trois quart environ. En regardant les messages, j'ai vu : &lt;tt class="docutils literal"&gt;Failed to mount '/system'&lt;/tt&gt;. J'ai testé pas mal de choses (revider les caches, changer le système de fichier avec parted, …) mais rien n'y a fait. Heureusement, j'ai fini par trouver &lt;a class="reference external" href="https://www.reddit.com/r/LineageOS/comments/5zf0yx/help_installing_lineageos_on_i9100_need_new_pit/"&gt;ce thread reddit&lt;/a&gt; qui donne la solution : réparer le fichier PIT du téléphone. Pour cela, &lt;a class="reference external" href="/static/lineageos/I9100_1.5GB-System_6GB-Data_512MB-Preload_by-the.gangster.pit"&gt;télécharger le PIT &amp;quot;qui va bien&amp;quot;&lt;/a&gt; (ou via &lt;a class="reference external" href="https://www.androidfilehost.com/?fid=24591000424954843"&gt;le lien original&lt;/a&gt;). Ensuite, lancer en mode &lt;em&gt;téléchargement&lt;/em&gt; (où &lt;tt class="docutils literal"&gt;boot.img&lt;/tt&gt; est le noyau de LineageOS dézipper précédemment):&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
heimdall&lt;span class="w"&gt; &lt;/span&gt;flash&lt;span class="w"&gt; &lt;/span&gt;--repartition&lt;span class="w"&gt; &lt;/span&gt;--pit&lt;span class="w"&gt; &lt;/span&gt;I9100_1.5GB-System_6GB-Data_512MB-Preload_by-the.gangster.pit&lt;span class="w"&gt; &lt;/span&gt;--KERNEL&lt;span class="w"&gt; &lt;/span&gt;boot.img&lt;span class="w"&gt; &lt;/span&gt;--RECOVERY&lt;span class="w"&gt; &lt;/span&gt;twrp-3.1.0-0-i9100.img
&lt;/pre&gt;
&lt;p&gt;À ce stade, l'installation du zip devrait fonctionner (j'ai revidé les caches avant on ne sait jamais).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Installer su&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Redémarrer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Normalement tout va bien&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Malheureusement, je ne sais plus à quel moment j'ai dû effacer la carte SD (je n'ai pas pris de notes pensant pouvoir écrire ce billet plus tôt). Mais de mémoire, c'était évident donc j'espère que pour vous aussi. Et si vous rencontrez un problème, vous pouvez laisser un commentaire !&lt;/p&gt;
</content><category term="Blog"></category><category term="Android"></category><category term="LineageOS"></category></entry><entry><title>Angular2 and SVG</title><link href="https://www.jujens.eu/posts/en/2017/Feb/21/angular2-svg/" rel="alternate"></link><published>2017-02-21T00:00:00+01:00</published><updated>2017-03-06T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2017-02-21:/posts/en/2017/Feb/21/angular2-svg/</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I use the &lt;a class="reference external" href="https://aurelia.io"&gt;Aurelia framework&lt;/a&gt;, a competitor of Angular2. I wrote &lt;a class="reference external" href="/tag/aurelia.html"&gt;several articles about Aurelia&lt;/a&gt;. I am not an expert with Angular2. If you spot a mistake, please leave a comment.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://medium.com/hashnode/rob-eisenberg-on-aurelia-and-how-it-stacks-up-against-angular-2-and-react-82721d714449"&gt;an interview&lt;/a&gt; about a year old, Rob Eisenberg, the creator of the &lt;a class="reference external" href="https://aurelia.io"&gt;Aurelia framework&lt;/a&gt;, said:&lt;/p&gt;
&lt;blockquote&gt;
Over a …&lt;/blockquote&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I use the &lt;a class="reference external" href="https://aurelia.io"&gt;Aurelia framework&lt;/a&gt;, a competitor of Angular2. I wrote &lt;a class="reference external" href="/tag/aurelia.html"&gt;several articles about Aurelia&lt;/a&gt;. I am not an expert with Angular2. If you spot a mistake, please leave a comment.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://medium.com/hashnode/rob-eisenberg-on-aurelia-and-how-it-stacks-up-against-angular-2-and-react-82721d714449"&gt;an interview&lt;/a&gt; about a year old, Rob Eisenberg, the creator of the &lt;a class="reference external" href="https://aurelia.io"&gt;Aurelia framework&lt;/a&gt;, said:&lt;/p&gt;
&lt;blockquote&gt;
Over a year ago the Angular 2 team introduced their symbolic binding syntax. While that was technically standards compliant HTML, it was pointed out by the community that it was not compliant SVG (I have not confirmed that myself). Although members of the community pointed this out, the Angular 2 team made no changes to their design.&lt;/blockquote&gt;
&lt;p&gt;Since I recently completed a book about Angular2 and that I heavily use SVG in my applications, I wanted to test it myself. So I created an app with the cli (you can install it with &lt;tt class="docutils literal"&gt;npm install &lt;span class="pre"&gt;-g&lt;/span&gt; &lt;span class="pre"&gt;angular-cli&amp;#64;1.0.0-beta.22-1&lt;/span&gt;&lt;/tt&gt;). Nothing fancy, just &lt;tt class="docutils literal"&gt;ng new &lt;span class="pre"&gt;test-svg&lt;/span&gt;&lt;/tt&gt;. I then created a small SVG with Inkscape (it just contains a rectangle) and copied it into the template of the application.&lt;/p&gt;
&lt;p&gt;I ran &lt;tt class="docutils literal"&gt;ng serve&lt;/tt&gt; to build the application, opened it in a browser and BOOM, first error:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
zone.js:388Unhandled Promise rejection: Template parse errors:
':sodipodi:namedview' is not a known element:
&lt;/pre&gt;
&lt;p&gt;From what I know, Angular2 has its own HTML parser (mostly to parse camel cased syntax like &lt;tt class="docutils literal"&gt;ngIf&lt;/tt&gt; which is not HTML compliant) and it fails if it encounters an HTML tag or attribute it doesn't know. While it's nice to help you spot typos in the name of your component, if a piece of HTML or SVG has a non standard tag or attribute, it will crash. Here it is not a big deal, my SVG is small, I can get rid of &lt;tt class="docutils literal"&gt;&amp;lt;sodipodi:namedview /&amp;gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&amp;lt;metadata /&amp;gt;&lt;/tt&gt;. But in one of my application, I need to display user uploaded SVGs. Once uploaded, I add AngularJS tags so I can display only certain elements of it depending on actions of the user. These SVGs will contain non standard tags like the Inkscape ones. How can I get rid of all of them so that Angular2 is happy? Whitelist? But what should I put in it? Blacklist? Same question. There are many SVG software out there, each may have its quirks.&lt;/p&gt;
&lt;p&gt;This made me wonder: what if I use a brand new HTML 5.1 tag? Will it crash? I tried the code below, and it worked. I guess the parser is already 5.1 compliant. But to support 5.2 you will probably need to update Angular.&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; // Start Of A New Accordion Element
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; TITLE OF ACCORDION ELEMENT &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    // Anything Here Will Be Displayed After Accordion Is Toggled To Open
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now that we have a working SVG, it is time to add Angular2 markup. Let's start simple and add an &lt;tt class="docutils literal"&gt;ngIf&lt;/tt&gt; attribute to show/hide the SVG based on the value of a variable:&lt;/p&gt;
&lt;pre class="code HTML literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngIf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;showSvg&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Let's add a button to toggle the value of the SVG:&lt;/p&gt;
&lt;pre class="code HTML literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="err"&gt;)=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;toggleSvg&lt;/span&gt;&lt;span class="err"&gt;()&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Toggle SVG&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And the &lt;tt class="docutils literal"&gt;toggleSvg&lt;/tt&gt; function:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nx"&gt;toggleSvg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showSvg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showSvg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And this works. The SVG is correctly displayed/hidden when I press the button.&lt;/p&gt;
&lt;p&gt;Next step: hide the SVG when I click on the rectangle. Let's add the proper attribute:&lt;/p&gt;
&lt;pre class="code HTML literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="err"&gt;)=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;toggleSvg&lt;/span&gt;&lt;span class="err"&gt;()&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If I click on the rectangle, the SVG is hidden as expected.&lt;/p&gt;
&lt;p&gt;Next step: display multiple rectangles. Let's add a &lt;tt class="docutils literal"&gt;rectTops&lt;/tt&gt; property to the component. It contains the y value for SVG rectangles. We will loop over it to display a rectangle for each value (and in a moment, we will try to bind the value to the &lt;tt class="docutils literal"&gt;y&lt;/tt&gt; attribute of our rectangles).&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nx"&gt;rectTops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;90&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="code HTML literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;let top of rectTops&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="err"&gt;)=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;hideSvg&lt;/span&gt;&lt;span class="err"&gt;()&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If we inspect the DOM, we see all the rectangles in it. Now, let's use the value:&lt;/p&gt;
&lt;pre class="code HTML literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;let top of rectTops&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="err"&gt;)=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;hideSvg&lt;/span&gt;&lt;span class="err"&gt;()&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="err"&gt;]=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And it fails:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Unhandled Promise rejection: Template parse errors:
Can't bind to 'y' since it isn't a known property of ':svg:rect'. (&amp;quot;
&lt;/pre&gt;
&lt;p&gt;You may say, no problem, let's use the mustache notation instead:&lt;/p&gt;
&lt;pre class="code HTML literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;let top of rectTops&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="err"&gt;)=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;hideSvg&lt;/span&gt;&lt;span class="err"&gt;()&amp;quot;&lt;/span&gt;
   &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ top }}&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;No luck, it crashes too with the same error. And &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;bind-y=&amp;quot;top&amp;quot;&lt;/span&gt;&lt;/tt&gt;? No luck either, still the same error.&lt;/p&gt;
&lt;p&gt;It appears that the correct way to do this is:&lt;/p&gt;
&lt;pre class="code HTML literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;ngFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;let top of rectTops&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="err"&gt;)=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;hideSvg&lt;/span&gt;&lt;span class="err"&gt;()&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;attr&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="err"&gt;]=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;
   &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Thanks &lt;a class="reference external" href="/posts/en/2017/Feb/21/angular2-svg/#isso-111"&gt;NexusVI&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="history"&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;2017-03-06: Give the solution of how to use binding with SVG attributes. Thanks &lt;a class="reference external" href="/posts/en/2017/Feb/21/angular2-svg/#isso-111"&gt;NexusVI&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="Angular2"></category><category term="SVG"></category></entry><entry><title>Rédiger ses rapports avec rst et sphinx</title><link href="https://www.jujens.eu/posts/2014/Aug/13/rapport-rst-sphinx/" rel="alternate"></link><published>2014-08-13T00:00:00+02:00</published><updated>2014-08-13T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2014-08-13:/posts/2014/Aug/13/rapport-rst-sphinx/</id><summary type="html">&lt;p&gt;Comme je l'ai déjà évoqué &lt;a class="reference external" href="/posts/2013/Aug/23/de-latex-a-orgmode/"&gt;ici&lt;/a&gt; et &lt;a class="reference external" href="/posts/2014/May/10/langages-balisage/"&gt;là&lt;/a&gt;, j'adore les langages à balisage
léger. Du coup, j'essaie de les utiliser partout où je peux. Mais de là à
rédiger un rapport avec table des matières, pages de garde et glossaire, il y a
un pas. Surtout face à la puissance …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Comme je l'ai déjà évoqué &lt;a class="reference external" href="/posts/2013/Aug/23/de-latex-a-orgmode/"&gt;ici&lt;/a&gt; et &lt;a class="reference external" href="/posts/2014/May/10/langages-balisage/"&gt;là&lt;/a&gt;, j'adore les langages à balisage
léger. Du coup, j'essaie de les utiliser partout où je peux. Mais de là à
rédiger un rapport avec table des matières, pages de garde et glossaire, il y a
un pas. Surtout face à la puissance de LaTeX.&lt;/p&gt;
&lt;p&gt;J'avais déjà évoqué org-mode &lt;a class="reference external" href="/posts/2013/Aug/23/de-latex-a-orgmode/"&gt;ici&lt;/a&gt; et
pourquoi j'envisageais de l'utiliser en lieu et place de LaTeX, à savoir :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Bonne intégration avec Emacs&lt;/li&gt;
&lt;li&gt;Syntaxe légère&lt;/li&gt;
&lt;li&gt;Possibilité d'export en LaTeX&lt;/li&gt;
&lt;li&gt;Possibilité d'insérer du code LaTeX&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mais comme je n'ai pas trouvé de bon support pour org-mode dans Pelican, je m'en
suis un peu détourné (je l'utilise toujours pour mes notes) au profit le
Restructured Text. Comme ce langage a été conçu pour remplacer LaTeX lors pour
la génération HTML de la documentation de python, il est particulièrement
puissant. J'ai donc décidé de l'utiliser pour rédiger mon rapport de stage.&lt;/p&gt;
&lt;p&gt;Je vais vous présenter tout d'abord Restructured Text (rst) puis sphinx (qui est
utilisé pour générer la documentation à partir de fichiers rst) et pourquoi je
conseille son utilisation pour un rapport plutôt que juste rst.&lt;/p&gt;
&lt;div class="section" id="restructured-text"&gt;
&lt;h2&gt;Restructured Text&lt;/h2&gt;
&lt;div class="section" id="presentation"&gt;
&lt;h3&gt;Présentation&lt;/h3&gt;
&lt;p&gt;La syntaxe est simple. Un bloc de texte constitue un paragraphe. On sépare les
paragraphes avec un saut de ligne. On peut écrire en &lt;em&gt;italique&lt;/em&gt; et en &lt;strong&gt;gras&lt;/strong&gt; :&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
&lt;span class="ge"&gt;*italique*&lt;/span&gt; et en &lt;span class="gs"&gt;**gras**&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;On peut faire des listes numérotées ou non (attention, les tirets et les dièses
doivent être correctement aligné et les sous listes doivent être séparées de la
liste par un saut de ligne) :&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
&lt;span class="m"&gt;-&lt;/span&gt; item 1&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;-&lt;/span&gt; item 2&lt;span class="w"&gt;
&lt;/span&gt;
  &lt;span class="m"&gt;#.&lt;/span&gt; Sous-liste numérotée&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="m"&gt;#.&lt;/span&gt; Un deuxième élément de la sous-liste numérotée
&lt;/pre&gt;
&lt;p&gt;On peut inclure des images :&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;image&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; /images/diagramme-fast-LaTeX.png&lt;span class="w"&gt;
&lt;/span&gt;           &lt;span class="nc"&gt;:alt:&lt;/span&gt; Un diagramme fast réalisé en LaTeX&lt;span class="w"&gt;
&lt;/span&gt;           &lt;span class="nc"&gt;:width:&lt;/span&gt; 150%
&lt;/pre&gt;
&lt;p&gt;du code :&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;code&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; python&lt;span class="w"&gt;

&lt;/span&gt;          print('Hello World')
&lt;/pre&gt;
&lt;p&gt;Il est tout à fait possible de découper son fichier en plusieurs fichiers plus
petits, puis de les inclure avec :&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;include&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; fichier.rst
&lt;/pre&gt;
&lt;p&gt;On peut également insérer des fichiers LaTeX avec la directive :&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; latex&lt;span class="w"&gt;
&lt;/span&gt;         &lt;span class="nc"&gt;:file:&lt;/span&gt; fast.tex
&lt;/pre&gt;
&lt;p&gt;La syntaxe complète est disponible &lt;a class="reference external" href="http://docutils.sourceforge.net/docs/user/rst/quickstart.html"&gt;en ligne&lt;/a&gt;. Elle peut
paraître déroutante au début, mais on s'y fait et dans l'ensemble, je la trouve
très bien faite (de même que la documentation d'ailleurs).&lt;/p&gt;
&lt;p&gt;On peut ensuite exporter son fichier en HTML ou LaTeX avec respectivement
&lt;tt class="docutils literal"&gt;rst2html&lt;/tt&gt; et &lt;tt class="docutils literal"&gt;rst2latex&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rediger-son-rapport"&gt;
&lt;h3&gt;Rédiger son rapport&lt;/h3&gt;
&lt;p&gt;Rien de bien fantastique, on entre son texte en respectant la syntaxe, on insère
la table des matière avec &lt;tt class="docutils literal"&gt;.. contents::&lt;/tt&gt; et on exporte le tout avec
&lt;tt class="docutils literal"&gt;rst2latex&lt;/tt&gt;. On compile ensuite ce fichier avec &lt;tt class="docutils literal"&gt;latexmk&lt;/tt&gt;. On obtient un
fichier Pdf tout ce qu'il y a de plus joli (normal me direz-vous, c'est LaTeX
qui l'a fait).&lt;/p&gt;
&lt;p&gt;Mais cela ne résout pas tout, comme :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Comment fait-on une page de garde ?&lt;/li&gt;
&lt;li&gt;Comment utilise-t-on une autre police ?&lt;/li&gt;
&lt;li&gt;Comment insère-t-on un glossaire ?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La réponse aux deux premières questions tient en un mot : template ! En effet,
&lt;tt class="docutils literal"&gt;rst2latex&lt;/tt&gt; dispose d'un argument &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--template&lt;/span&gt;&lt;/tt&gt; au quel on peut passer un
fichier contenant du code LaTeX et des variables rst pour personnaliser le
fichier pdf.&lt;/p&gt;
&lt;p&gt;Le fichier template de base s'appelle default.tex et se trouve ici :
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/lib/python3.3/site-packages/docutils/writers/latex2e/default.tex&lt;/span&gt;&lt;/tt&gt; (sur
mon PC du moins). Il est assez basique :&lt;/p&gt;
&lt;pre class="code latex literal-block"&gt;
&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;head_prefix&lt;/span&gt;&lt;span class="c"&gt;% generated by Docutils &amp;lt;http://docutils.sourceforge.net/&amp;gt;
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="nb"&gt;{fixltx&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;e} &lt;/span&gt;&lt;span class="c"&gt;% LaTeX patches, \textsubscript
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="nb"&gt;{cmap} &lt;/span&gt;&lt;span class="c"&gt;% fix search and cut-and-paste in Acrobat
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;requirements
&lt;span class="c"&gt;%%% Custom LaTeX preamble
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;latex_preamble
&lt;/span&gt;&lt;span class="c"&gt;%%% User specified packages and stylesheets
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;stylesheet
&lt;span class="c"&gt;%%% Fallback definitions for Docutils-specific commands
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;fallbacks&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;pdfsetup
&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;titledata
&lt;/span&gt;&lt;span class="c"&gt;%%% Body
&lt;/span&gt;&lt;span class="nv"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{document}
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;body&lt;span class="nb"&gt;_&lt;/span&gt;pre&lt;span class="nb"&gt;_&lt;/span&gt;docinfo&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;docinfo&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;dedication&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;abstract&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;body
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Il n'y plus qu'à le personnaliser avec du code LaTeX pour qu'il réponde à nos
besoins :&lt;/p&gt;
&lt;pre class="code latex literal-block"&gt;
&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;head_prefix&lt;/span&gt;&lt;span class="c"&gt;% generated by Docutils &amp;lt;http://docutils.sourceforge.net/&amp;gt;
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="nb"&gt;{fixltx&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;e} &lt;/span&gt;&lt;span class="c"&gt;% LaTeX patches, \textsubscript
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="nb"&gt;{cmap} &lt;/span&gt;&lt;span class="c"&gt;% fix search and cut-and-paste in Acrobat
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;raccourcis&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;{fast&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;diagram}
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;french&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;{babel}
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="nb"&gt;{titlesec}
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="nb"&gt;paper,left&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;cm,right&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;cm,top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;cm,bottom&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;{geometry}
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;requirements
&lt;span class="c"&gt;%%% Custom LaTeX preamble
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;latex_preamble
&lt;/span&gt;&lt;span class="c"&gt;%%% User specified packages and stylesheets
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;stylesheet
&lt;span class="c"&gt;%%% Fallback definitions for Docutils-specific commands
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;fallbacks&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;pdfsetup
&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;titledata
&lt;/span&gt;&lt;span class="c"&gt;%%% Custom font
&lt;/span&gt;&lt;span class="nv"&gt;\usepackage&lt;/span&gt;&lt;span class="nb"&gt;{libertine}
&lt;/span&gt;&lt;span class="c"&gt;%%% Set numeration
&lt;/span&gt;&lt;span class="nv"&gt;\setcounter&lt;/span&gt;&lt;span class="nb"&gt;{secnumdepth}{&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="nb"&gt;}
&lt;/span&gt;&lt;span class="c"&gt;%%% Body
&lt;/span&gt;&lt;span class="nv"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{document}
&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;body&lt;span class="nb"&gt;_&lt;/span&gt;pre&lt;span class="nb"&gt;_&lt;/span&gt;docinfo&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;docinfo&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;dedication&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="nb"&gt;abstract&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;body
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Je précise que l'ordre avec lequel vous insérer votre code et les variables est
important. Si on met la ligne &lt;tt class="docutils literal"&gt;\usepackage{libertine}&lt;/tt&gt; (qui change la police)
juste après les autres &lt;tt class="docutils literal"&gt;usepackage&lt;/tt&gt; le choix de ma police sera écrasée par
celle de Rst.&lt;/p&gt;
&lt;p&gt;Je propose un exemple plus complet en &lt;a class="reference external" href="https://www.jujens.eu/static/rapport-rst-sphinx/template.tex"&gt;téléchargement&lt;/a&gt; (avec notamment une page
de garde personnalisée).&lt;/p&gt;
&lt;p&gt;On peut également utiliser les options &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--document-class&lt;/span&gt;&lt;/tt&gt; et
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--document-options&lt;/span&gt;&lt;/tt&gt; pour personnaliser le document. Il existe bien d'autres
options, je vous laisse regarder la sortie de &lt;tt class="docutils literal"&gt;rst2latex &lt;span class="pre"&gt;-h&lt;/span&gt;&lt;/tt&gt; pour la liste
complète.&lt;/p&gt;
&lt;p&gt;On générera le fichier .tex avec :&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
rst2latex
rapport.rst --template=template.tex --documentclass=report --documentoptions=12pt
&lt;/pre&gt;
&lt;p&gt;Et pour le glossaire ? Ce n'est pas possible, faute de syntaxe approprié,
directement avec rst. Ça viendra peut être un jour, c'est sur la &lt;a class="reference external" href="http://docutils.sourceforge.net/docs/dev/todo.html"&gt;todo list des
dévoleppeurs&lt;/a&gt;. C'est là
que sphinx entre en jeu.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="sphinx"&gt;
&lt;h2&gt;Sphinx&lt;/h2&gt;
&lt;div class="section" id="presentation-1"&gt;
&lt;h3&gt;Présentation&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://sphinx-doc.org/"&gt;sphinx&lt;/a&gt; est un générateur de documentation pour
python. Il utilise la syntaxe rst et peut produire des documents HTML, pdf, des
pages man, etc. Il propose également des extensions à la syntaxe de base comme
&lt;tt class="docutils literal"&gt;term&lt;/tt&gt; et &lt;tt class="docutils literal"&gt;glossary&lt;/tt&gt; pour un glossaire et quelques divergences. Par exemple,
la table des matières ne se génère plus avec &lt;tt class="docutils literal"&gt;.. contents::&lt;/tt&gt; mais avec
&lt;tt class="docutils literal"&gt;.. toctree::&lt;/tt&gt; et les inclusions de fichier se font avec
&lt;tt class="docutils literal"&gt;.. raw::&lt;/tt&gt;. Typiquement, on utilise un Makefile pour automatiser la
génération, Makefile que sphinx est capable de créer tout seul.&lt;/p&gt;
&lt;p&gt;Mais rassurez-vous, dans l'ensemble tout fonctionne quasiment comme avec
rst. Lorsque je suis passé de rst brut à sphinx, mon seul problème a été
l'utilisation des mes entêtes LaTeX personnalisées.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rediger-son-rapport-1"&gt;
&lt;h3&gt;Rédiger son rapport&lt;/h3&gt;
&lt;p&gt;Il faut tout d'abord initialiser sphinx, avec &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;sphinx-quickstart&lt;/span&gt;&lt;/tt&gt;. Le programme
va alors vous poser plusieurs questions pour générer une configuration de base.&lt;/p&gt;
&lt;p&gt;Le rapport en lui même se rédige comme sous rst avec les mêmes possibilités
d'inclusion de fichiers. Attention toute fois, l'inclusion est relative au
Makefile alors qu'aupapravant, elle était relative au fichier d'inclusion.&lt;/p&gt;
&lt;p&gt;Lors de mon passage à sphinx, peu importe l'endroit où j'insérais la directive
&lt;tt class="docutils literal"&gt;.. toctree::&lt;/tt&gt; la table des matières se retrouvait en début de fichier alors
que je la voulais après l'introduction et le résumé. Qu'à cela ne tienne, la
directive &lt;tt class="docutils literal"&gt;.. raw::&lt;/tt&gt; m'a permit d'insérer la commande LaTeX
&lt;tt class="docutils literal"&gt;\tableofcontents&lt;/tt&gt; où je voulais.&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; latex&lt;span class="w"&gt;

&lt;/span&gt;         \tableofcontents
&lt;/pre&gt;
&lt;p&gt;Et pour le glossaire alors ? Rien de plus simple :&lt;/p&gt;
&lt;pre class="code rst literal-block"&gt;
J'ulitise le &lt;span class="na"&gt;:term:&lt;/span&gt;&lt;span class="nv"&gt;`CMS`&lt;/span&gt; &lt;span class="na"&gt;:term:&lt;/span&gt;&lt;span class="nv"&gt;`Drupal`&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;glossary&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;   CMS&lt;span class="w"&gt;
&lt;/span&gt;     Acronyme de &lt;span class="ge"&gt;*Content Management System*&lt;/span&gt;, soit système de gestion de&lt;span class="w"&gt;
&lt;/span&gt;     contenu.&lt;span class="w"&gt;

&lt;/span&gt;   Drupal&lt;span class="w"&gt;
&lt;/span&gt;     Un des &lt;span class="na"&gt;:term:&lt;/span&gt;&lt;span class="nv"&gt;`CMS`&lt;/span&gt; les plus connus et réputé. Il est utilisé par de&lt;span class="w"&gt;
&lt;/span&gt;     nombreuses entreprises et institutions à travers le monde.
&lt;/pre&gt;
&lt;p&gt;Le seul point qui m'a posé problème c'est la configuration. Elle est quasi
intégralement géré pour l'export LaTeX par le dictionnaire
&lt;tt class="docutils literal"&gt;latex_elements&lt;/tt&gt;. Par exemple, pour moi :&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;preamble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'''\usepackage&lt;/span&gt;&lt;span class="si"&gt;{fixltx2e}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;% La&lt;/span&gt;&lt;span class="s1"&gt;TeX patches, \textsubscript
\usepackage&lt;/span&gt;&lt;span class="si"&gt;{cmap}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;% f&lt;/span&gt;&lt;span class="s1"&gt;ix search and cut-and-paste in Acrobat
\usepackage[raccourcis]{fast-diagram}
\usepackage&lt;/span&gt;&lt;span class="si"&gt;{titlesec}&lt;/span&gt;&lt;span class="s1"&gt;
\usepackage[a4paper,left=2cm,right=2cm,top=2cm,bottom=2cm]&lt;/span&gt;&lt;span class="si"&gt;{geometry}&lt;/span&gt;&lt;span class="s1"&gt;
&lt;/span&gt;&lt;span class="si"&gt;%%&lt;/span&gt;&lt;span class="s1"&gt;% Redifined titleformat
\setlength{\parindent}&lt;/span&gt;&lt;span class="si"&gt;{0cm}&lt;/span&gt;&lt;span class="s1"&gt;
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
\newcommand{\hsp}{\hspace&lt;/span&gt;&lt;span class="si"&gt;{20pt}&lt;/span&gt;&lt;span class="s1"&gt;}
\newcommand{\HRule}{\rule{\linewidth}&lt;/span&gt;&lt;span class="si"&gt;{0.5mm}&lt;/span&gt;&lt;span class="s1"&gt;}
\titleformat{\chapter}[hang]{\Huge\bfseries\sffamily}{\thechapter\hsp}&lt;/span&gt;&lt;span class="si"&gt;{0pt}&lt;/span&gt;&lt;span class="s1"&gt;{\Huge\bfseries\sffamily}
&lt;/span&gt;&lt;span class="si"&gt;%%&lt;/span&gt;&lt;span class="s1"&gt;% Custom font
\usepackage&lt;/span&gt;&lt;span class="si"&gt;{libertine}&lt;/span&gt;&lt;span class="s1"&gt;
&lt;/span&gt;&lt;span class="si"&gt;%%&lt;/span&gt;&lt;span class="s1"&gt;% Set numeration
\setcounter&lt;/span&gt;&lt;span class="si"&gt;{secnumdepth}{3}&lt;/span&gt;&lt;span class="s1"&gt;
'''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;latex_elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# The paper size ('letterpaper' or 'a4paper').&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'papersize'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'a4paper'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# The font size ('10pt', '11pt' or '12pt').&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'pointsize'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'12pt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Babel for french&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'babel'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;usepackage[french]&lt;/span&gt;&lt;span class="si"&gt;{babel}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Additional stuff for the LaTeX preamble.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'preamble'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;preamble&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# No default title&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'maketitle'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# No default toc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'tableofcontents'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;La classe du document se spécifie dans la variable &lt;tt class="docutils literal"&gt;latex_documents&lt;/tt&gt; :&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;latex_documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rapport'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rapport.tex'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rapport de stage 2A'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="s1"&gt;'Julien Enselme'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'manual'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Les classes habituelles de LaTeX ne sont pas compatibles avec sphinx. Il faut
soit utiliser celles par défaut (manual pour un rapport ou how-to pour un
article) soit les personnaliser.&lt;/p&gt;
&lt;p&gt;Je pense qu'à partir de cet exemple, vous pourrez subvenir à vos besoins
facilement. Sinon, vous pouvez toujours poster un commentaire.&lt;/p&gt;
&lt;p&gt;Vous trouverez dans &lt;a class="reference external" href="http://sphinx-doc.org/config.html#options-for-latex-output"&gt;la documentation complète&lt;/a&gt; toutes des
options que je n'ai pas pu vous présenter ici.&lt;/p&gt;
&lt;p&gt;Je vous propose également un &lt;a class="reference external" href="https://www.jujens.eu/static/rapport-rst-sphinx/conf.py"&gt;un fichier de configuration complet&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;J'espère vous avoir convaincu d'utiliser la puissance et la légèreté de rst et
sphinx pour rédiger vos rapports, avec un peu de LaTeX brut par endroit (faute
de mieux) en lieu et place des WYSIWYG.&lt;/p&gt;
&lt;p&gt;Même si abandonner LaTeX peut faire un peu peur et que la configuration de
sphinx est un peu ardue sans exemple complet, je ne regrette pas mon choix et
suis bien content d'utiliser rst (même si je dois reconnaître qu'il est dommage
que sphinx ne puisse utiliser les classes de base de LaTeX) !&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="python"></category><category term="Rst"></category><category term="sphinx"></category></entry><entry><title>Comment supprimer toutes les tables mysql qui contiennent un motif</title><link href="https://www.jujens.eu/posts/2014/Jul/30/supprimer_plusieurs_tables_mysql_motif/" rel="alternate"></link><published>2014-07-30T00:00:00+02:00</published><updated>2014-07-30T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2014-07-30:/posts/2014/Jul/30/supprimer_plusieurs_tables_mysql_motif/</id><summary type="html">&lt;p&gt;Récemment, j'ai eu besoin de faire du ménage dans les tables d'une base de
données. Toutes les tables à supprimer commençaient par le même
motif. Malheureusement, mysql ne permet pas d'utiliser le jocker &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; dans une
requête &lt;tt class="docutils literal"&gt;DROP&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Heureusement la solution est simple. Il suffit de lister toutes les tables ayant …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Récemment, j'ai eu besoin de faire du ménage dans les tables d'une base de
données. Toutes les tables à supprimer commençaient par le même
motif. Malheureusement, mysql ne permet pas d'utiliser le jocker &lt;tt class="docutils literal"&gt;%&lt;/tt&gt; dans une
requête &lt;tt class="docutils literal"&gt;DROP&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Heureusement la solution est simple. Il suffit de lister toutes les tables ayant
ce motif grâce à la requête &lt;tt class="docutils literal"&gt;SHOW TABLES LIKE 'motif%'&lt;/tt&gt;. Reste à boucler sur
ces tables pour les supprimer. D'où le code suivant :&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="nv"&gt;motif&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;del_&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;table&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mysql&lt;span class="w"&gt; &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;-NBe&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SHOW TABLES LIKE '&lt;/span&gt;&lt;span class="nv"&gt;$motif&lt;/span&gt;&lt;span class="s2"&gt;%'&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;mysql&lt;span class="w"&gt; &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DROP TABLE &lt;/span&gt;&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;L'option &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-N&lt;/span&gt;&lt;/tt&gt; de mysql masque les entêtes de colonnes et l'option &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-B&lt;/span&gt;&lt;/tt&gt;
permet d'avoir un résultat brut, exploitable dans la boucle.&lt;/p&gt;
&lt;p&gt;On peut également faire &lt;a class="reference external" href="https://www.jujens.eu/static/supprimer_plusieurs_tables_mysql_motif/mysql_drop_table_like.sh"&gt;le script plus générique suivant&lt;/a&gt; :&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="ch"&gt;#!/usr/bin/bash
&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mysql_drop_table_like [options] database pattern

Drop all the mysql tables in database that starts with pattern.

-h: host
-u: user
-pPASSWD: give password
-P: prompt for password
&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;usage&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$help&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;pflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;Pflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;uflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;hflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;getopts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;:h:u:p:P&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opt&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$opt&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;h&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$OPTARG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;hflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;true&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;u&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$OPTARG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;uflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;true&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;p&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;passwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$OPTARG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;true&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;P&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;Pflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;true&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;:&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Option -&lt;/span&gt;&lt;span class="nv"&gt;$OPTARG&lt;/span&gt;&lt;span class="s2"&gt; requires an argument.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;usage&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="se"&gt;\?&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;usage&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;esac&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;shift&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="nv"&gt;OPTIND&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# To get the 1st positional argument with $1
&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Check that we have at least 2 positional arguments
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-lt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Number of positional arguments insuffisant.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;usage&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Cannot provide and ask for password
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Pflag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pflag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cannot provide and ask for password.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;usage&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$hflag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-h &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pflag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;PASSWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-p&lt;/span&gt;&lt;span class="nv"&gt;$passwd&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Pflag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;PASSWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-p&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uflag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-u &lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mysql &lt;/span&gt;&lt;span class="nv"&gt;$HOST&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PASSWD&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;table&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$mysql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-NBe&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SHOW TABLES LIKE '&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;%'&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$mysql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DROP TABLE &lt;/span&gt;&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/pre&gt;
</content><category term="Blog"></category></entry><entry><title>Utiliser isso pour avoir des commentaires auto-hébergés</title><link href="https://www.jujens.eu/posts/2014/Jun/08/commentaires-isso/" rel="alternate"></link><published>2014-06-08T00:00:00+02:00</published><updated>2014-06-08T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2014-06-08:/posts/2014/Jun/08/commentaires-isso/</id><summary type="html">&lt;p&gt;Lorsque j'ai lancé ce blog, j'utilisais
&lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/tree/master/pelican_comment_system"&gt;un plugin de pelican&lt;/a&gt;
pour avoir un système de commentaires statiques. Je ne suis en effet pas
un grand fan des commentaires hébergés chez des prestataires externes
comme disqus pour des questions de vie privée. Ce plugin me semblait un
bon compromis : il ajoute …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lorsque j'ai lancé ce blog, j'utilisais
&lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/tree/master/pelican_comment_system"&gt;un plugin de pelican&lt;/a&gt;
pour avoir un système de commentaires statiques. Je ne suis en effet pas
un grand fan des commentaires hébergés chez des prestataires externes
comme disqus pour des questions de vie privée. Ce plugin me semblait un
bon compromis : il ajoute un formulaire grâce à du javascript dans
lequel on peut entrer son commentaire qui est ensuite envoyé par mail à
l'administrateur du site. Celui-ci doit ensuite copier/coller le contenu
du commentaire dans un fichier dédié.&lt;/p&gt;
&lt;p&gt;Cela présente l'avantage d'être simple et respectueux de la vie privée
bien qu'un peu fastidieux pour l'administrateur. Mais quand on a pas
beaucoup de commentaire, ce n'est pas très grave et ça permet de les
modérer.&lt;/p&gt;
&lt;p&gt;Je me disais que c'était très bien et puis récemment, je suis tombé sur
&lt;a class="reference external" href="http://hackriculture.fr/isso-commentaires-auto-heberges-pour-pelican-et-autres-sites-statiques.html"&gt;cet article&lt;/a&gt;
qui présente issos. Isso est un petit logiciel qui vous permet de
stocker les commentaires dans une base de données sqlite et de les
enregistrer/afficher grâce à un peu de javascript. Cela vous permet de
combiner les avantages de l'auto-hébergement et de l'aspect dynamique
d'un service comme disqus. Petit plus sympa, isso peut vous envoyer un
mail à chaque commentaire ajouté avec la possibilité de les modérer.&lt;/p&gt;
&lt;p&gt;C'est simple, efficace et d'après mes tests ça marche plutôt bien.
Évidement, ce n'est pas utilisable si vous avez vraiment beaucoup de
commentaires, ça reste du sqlite ;-). Il manque également une vraie
interface d'administration pour gérer les commentaires.&lt;/p&gt;
&lt;p&gt;Voilà un exemple basique de configuration.&lt;/p&gt;
&lt;pre class="code ini literal-block"&gt;
&lt;span class="k"&gt;[general]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# File location to the SQLite3 database, highly recommended&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# to change this location to a non-temporary location!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;dbpath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/var/lib/isso/comments.db&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# URL to your website. When you start Isso, it will probe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# your website with a simple GET / request to see if it can&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# reach the webserver. If this fails, Isso may not be able&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# check if a web page exists, thus fails to accept new&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# comments.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# You can supply more than one host.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://www.jujens.eu&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;smtp&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[server]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080/&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[moderation]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Enable comment moderation queue. This option only affects&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# new comments. Comments in modertion queue are not visible&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# to other users until you activate them.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Pour lancer isso en arrière plan avec sa configuration&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/usr/local/bin/isso -c /etc/isso.conf run &amp;amp;
&lt;/pre&gt;
&lt;div class="section" id="pour-en-savoir-plus"&gt;
&lt;h2&gt;Pour en savoir plus&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;L'article cité plus haut :&lt;/strong&gt;
&lt;a class="reference external" href="http://hackriculture.fr/isso-commentaires-auto-heberges-pour-pelican-et-autres-sites-statiques.html"&gt;http://hackriculture.fr/isso-commentaires-auto-heberges-pour-pelican-et-autres-sites-statiques.html&lt;/a&gt;
Le site officiel d'isso : &lt;a class="reference external" href="http://posativ.org/isso/"&gt;http://posativ.org/isso/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="pelican"></category><category term="python"></category></entry><entry><title>Markdown vs creole vs Restructured Text vs org-mode</title><link href="https://www.jujens.eu/posts/2014/May/10/langages-balisage/" rel="alternate"></link><published>2014-05-10T00:00:00+02:00</published><updated>2014-05-10T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2014-05-10:/posts/2014/May/10/langages-balisage/</id><summary type="html">&lt;p&gt;Comme je l'ai déjà détaillé
&lt;a class="reference external" href="/posts/2013/Aug/23/de-latex-a-orgmode/"&gt;ici&lt;/a&gt;, je ne suis pas un
grand fan des syntaxes verbeuses à la LaTeX ou HTML et je préfère les
langages à balisages légers (et oui, je ne suis pas non plus fan des
WYSIWYG). Comme j'ai eu l'occasion d'en tester plusieurs ces derniers
temps …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Comme je l'ai déjà détaillé
&lt;a class="reference external" href="/posts/2013/Aug/23/de-latex-a-orgmode/"&gt;ici&lt;/a&gt;, je ne suis pas un
grand fan des syntaxes verbeuses à la LaTeX ou HTML et je préfère les
langages à balisages légers (et oui, je ne suis pas non plus fan des
WYSIWYG). Comme j'ai eu l'occasion d'en tester plusieurs ces derniers
temps, je vous propose une petite comparaison.&lt;/p&gt;
&lt;div class="section" id="markdown"&gt;
&lt;h2&gt;Markdown&lt;/h2&gt;
&lt;p&gt;Peut être le plus connus et le plus utilisé. On le retrouve sur
&lt;a class="reference external" href="http://github.com"&gt;Github&lt;/a&gt;,
&lt;a class="reference external" href="http://stackoverflow.com"&gt;Stackoverflow&lt;/a&gt;,
&lt;a class="reference external" href="http://getpelican.com"&gt;pelican&lt;/a&gt; et dans les commentaires de ce blog
:D. La syntaxe est simple ce qui doit expliquer sa popularité.&lt;/p&gt;
&lt;p&gt;Malheureusement, le markdown de base n'est pas très complet. Il ne gère
pas les tableaux par exemple. Certes, on peut insérer du HTML dans du
code markdown pour palier ce problème mais bon, le but c'est justement
de pouvoir utiliser quelque chose de plus léger que le HTML. Cela a donc
assez logiquement entraîné la création de diverses extensions comme
&lt;a class="reference external" href="http://github.github.com/github-flavored-markdown/"&gt;le markdow à la sauce github&lt;/a&gt;
qui ne sont évidement pas toujours compatibles entre elle.&lt;/p&gt;
&lt;p&gt;Ce langage contient aussi un point que je trouve particulièrement mal
fait : pour insérer un saut de ligne, on peut ajouter deux espaces à la
fin de la dite ligne. Pour moi c'est très mauvais : on ne voit pas si on
a modifié ces espaces et tous mes éditeurs de texte sont paramétrés pour
enlever les espaces de fin de ligne.&lt;/p&gt;
&lt;p&gt;Mais ce qui m'a fait fuir ce langage c'est la possibilité d'ajouter du
code que je trouve vraiment pourrie. On peut ajouter du code, soit en
l'entourant avec &lt;tt class="docutils literal"&gt;~~~&lt;/tt&gt; soit en ajoutant deux tabulations devant.
J'avais déjà testé la syntaxe avec les tildes et ça marchait plutôt
bien. Puis en voulant ajouter du code sur ce blog j'ai eu des problèmes
pour activer la coloration syntaxique si l'extension table des matières
est active. La syntaxe avec les tildes ne fonctionnait plus et j'ai bien
galéré avec l'autre. Du coup, j'ai regardé d'autres syntaxes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="restructured-text"&gt;
&lt;h2&gt;Restructured Text&lt;/h2&gt;
&lt;p&gt;L'autre syntaxe bien supportée par Pelican est le
&lt;a class="reference external" href="http://docutils.sourceforge.net/docs/user/rst/quickref.html"&gt;Restructured Text&lt;/a&gt;
(rst en abrégé). C'est beaucoup plus complet que markdown et ça supporte
beaucoup de chose : ajouter des propriétés aux images insérées, les
références, sommaire et index… Il faut dire que le langage a été conçu
pour remplacer LaTeX pour la documentation de python. Donc,
mécaniquement, il faut pas mal d'options. Et je dois dire que le
résultat, du point de vue des fonctionnalités offertes en est un
concurrent sérieux pour l'export en html. LaTeX garde l'avantage pour
l'export pdf car c'est aussi un système de mise en page très pointu (ce
dont on a pas forcément besoin pour autre chose que la page de garde
cela dit).&lt;/p&gt;
&lt;p&gt;Pour être franc, mes premiers tests avec rst n'ont pas été concluant du
tout. Premièrement, le fait qu'il soit nécessaire de souligner ces
titres avec =, -, ou + suivant le niveau ne me plaît pas
particulièrement, je trouve ça un peu lourd. Ici c'est d'autant plus
lourd que le soulignement doit faire exactement la taille du titre,
sinon rst génère une erreur.&lt;/p&gt;
&lt;p&gt;Ensuite, les sous-listes doivent être séparées de la liste parente par
un saut de ligne et la puce doit être indentée précisément sous le
premier caractère du texte de la liste parente. Le calvaire des
indentations en python mais en pire !&lt;/p&gt;
&lt;p&gt;Enfin, les tableaux doivent être fait intégralement à la main.&lt;/p&gt;
&lt;p&gt;Et puis j'ai trouvé
&lt;a class="reference external" href="http://www.inec.us/blog/2013/01/26/restructuredtext-markup-you-should-know-about/"&gt;cet article (en)&lt;/a&gt;
qui m'a réconcilié avec le format. J'utilise désormais le
&lt;a class="reference external" href="http://docutils.sourceforge.net/docs/user/emacs.html"&gt;rst-mode&lt;/a&gt; pour
emacs qui permet de placer le bon nombre de caractères sous les titres
avec &lt;tt class="docutils literal"&gt;^u =&lt;/tt&gt;, fait l'indentation tout seul. J'ai aussi appris que emacs
est capable sans plugin supplémentaire de créer des tableaux ascii
compatible avec rst. J'ai donc retenté l'expérience et ce fut concluant.
Maintenant, j'admets, j'aime bien et j'ai pris plaisir à écrire
&lt;a class="reference external" href="/posts/2014/May/05/Communication-serie/"&gt;mon tuto sur la communication série en arduino&lt;/a&gt;
avec. Par contre, compte tenu de sa relative verbosité, je le réserve
pour les longs articles ou les tutos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creole"&gt;
&lt;h2&gt;Creole&lt;/h2&gt;
&lt;p&gt;Encore un autre langage à balise. Il a été créé dans le but d'unifier
les langages existants. Il est léger et plutôt complet. Voir par
&lt;a class="reference external" href="http://www.wikicreole.org/wiki/Creole1.0"&gt;là&lt;/a&gt; pour la syntaxe.
J'admets avoir été séduit par ce langage simple et efficace. J'ai même
fait
&lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/tree/master/creole_reader"&gt;le plugin pelican&lt;/a&gt;
pour l'utiliser. Je l'utilise pour les articles de blog assez courts qui
n'ont pas besoin de la puissance de rst.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="org-mode"&gt;
&lt;h2&gt;Org-mode&lt;/h2&gt;
&lt;p&gt;Je n'ai pas grand chose de plus à dire par rapport à mon dernier article
: &lt;a class="reference external" href="/posts/2013/Aug/23/de-latex-a-orgmode/"&gt;de LaTeX à org-mode&lt;/a&gt;.
C'est complet et bien intégré avec emacs. Si je m'en suis un peu
détourné c'est à cause du manque d'implémentation dans pelican. Il faut
impérativement passer par emacs et on perd la possibilité d'utiliser le
système de cache de pelican.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Le moins que l'on puisse dire c'est qu'on a énormément de choix. Je suis
personnellement très déçu par mardown et j'ai du mal à comprendre
pourquoi il est si populaire alors que d'autres langages comme creole
sont vraiment bien.&lt;/p&gt;
&lt;p&gt;Je pense également utiliser rst pour mes prochains rapport plutôt que
LaTeX ou org-mode. Je trouve ce format mieux fait que org-mode bien
qu'un peu plus verbeux. Mais je vais certainement continuer à utiliser
org-mode pour mes notes.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="markup"></category><category term="Markdown"></category><category term="Creole"></category><category term="Rst"></category><category term="org-mode"></category><category term="emacs"></category></entry><entry><title>Une petite sélection de jeu libre pour se détendre</title><link href="https://www.jujens.eu/posts/2014/May/07/selection-jeux-libres/" rel="alternate"></link><published>2014-05-07T00:00:00+02:00</published><updated>2014-05-07T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2014-05-07:/posts/2014/May/07/selection-jeux-libres/</id><summary type="html">&lt;p&gt;Durant les dernières vacances, j'ai voulu me détendre avec quelques jeux
libres. J'en ai retenu 3 qui sont simples et distrayants. J'ai donc
exclu &lt;a class="reference external" href="http://play0ad.com/"&gt;0ad&lt;/a&gt; et
&lt;a class="reference external" href="http://www.wesnoth.org/"&gt;Battle for Wesnoth&lt;/a&gt; qui sont des jeux de
stratégie un peu trop « prise de tête », pas assez simples (mais très
bons quand même).&lt;/p&gt;
&lt;div class="section" id="supertuxkart"&gt;
&lt;h2&gt;SuperTuxKart …&lt;/h2&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Durant les dernières vacances, j'ai voulu me détendre avec quelques jeux
libres. J'en ai retenu 3 qui sont simples et distrayants. J'ai donc
exclu &lt;a class="reference external" href="http://play0ad.com/"&gt;0ad&lt;/a&gt; et
&lt;a class="reference external" href="http://www.wesnoth.org/"&gt;Battle for Wesnoth&lt;/a&gt; qui sont des jeux de
stratégie un peu trop « prise de tête », pas assez simples (mais très
bons quand même).&lt;/p&gt;
&lt;div class="section" id="supertuxkart"&gt;
&lt;h2&gt;SuperTuxKart&lt;/h2&gt;
&lt;p&gt;C'est un jeu de course qui ressemble au célèbre Mario Kart. On y joue
seul ou à plusieurs (pas pu tester faute de manette). On a le choix
entre plusieurs karts qui s'inspirent tous d'un projet libre. On
retrouve donc Tux, Beastie, ElePHPant, … Le jeu dispose d'un mode
histoire : le but étant de gagner les courses proposées pour libérer
GNU. Le scénario n'est donc pas très profond, mais ce n'est pas ce qu'on
lui demande.&lt;/p&gt;
&lt;p&gt;Le jeu contient tous les éléments qui font qu'un jeu de course comme ça
est bien : nitro, trucs à lancer sur les adversaires (ballon de basket,
cake explosif,…), plutôt bons graphismes et de bonnes musiques qui
s'accordent bien avec la course.&lt;/p&gt;
&lt;div class="section" id="captures"&gt;
&lt;h3&gt;Captures&lt;/h3&gt;
&lt;p&gt;&lt;img alt="STK 1" src="/images/selection-jeux-libres/SuperTuxKart-00.resized.png" /&gt; &lt;img alt="STK 2" src="/images/selection-jeux-libres/SuperTuxKart-01.resized.png" /&gt; &lt;img alt="STK 3" src="/images/selection-jeux-libres/SuperTuxKart-02.resized.png" /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="liens-et-telechargements"&gt;
&lt;h3&gt;Liens et téléchargements&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Site officiel : &lt;a class="reference external" href="http://supertuxkart.sourceforge.net/"&gt;http://supertuxkart.sourceforge.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Téléchargement (Disponible dans les dépôts de la plupart des
distributions) : &lt;a class="reference external" href="http://supertuxkart.sourceforge.net/Downloads"&gt;http://supertuxkart.sourceforge.net/Downloads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="funguloids"&gt;
&lt;h2&gt;Funguloïds&lt;/h2&gt;
&lt;p&gt;C'est un jeu assez étrange. Vous êtes une sorte de comète et vous devez
collecter des champignons de couleur pour les ramener sur leur platforme
tout en évitant des astéroïdes. Vous gagnez alors des points et changer
de carte. Cela étant dit, le jeu est vraiment très beau graphiquement et
sa musique calme vous permettra de vous reposer tout en vous amusant.&lt;/p&gt;
&lt;div class="section" id="captures-1"&gt;
&lt;h3&gt;Captures&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Funguloïds 1" src="/images/selection-jeux-libres/Funguloids-00.resized.png" /&gt; &lt;img alt="Funguloïds 2" src="/images/selection-jeux-libres/Funguloids-01.resized.png" /&gt; &lt;img alt="Funguloïds 3" src="/images/selection-jeux-libres/Funguloids-02.resized.png" /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="liens-et-telechargements-1"&gt;
&lt;h3&gt;Liens et téléchargements&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Site officiel : &lt;a class="reference external" href="http://funguloids.sourceforge.net/"&gt;http://funguloids.sourceforge.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Téléchargement (Disponible dans les dépôts de la plupart des
distributions) : &lt;a class="reference external" href="http://funguloids.sourceforge.net/download.html"&gt;http://funguloids.sourceforge.net/download.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="supertux-2"&gt;
&lt;h2&gt;SuperTux 2&lt;/h2&gt;
&lt;p&gt;Pour faire simple : c'est Tux qui se prend pour Mario sur la banquise.
C'est un bon jeu de platforme, simple mais efficace.&lt;/p&gt;
&lt;div class="section" id="captures-2"&gt;
&lt;h3&gt;Captures&lt;/h3&gt;
&lt;p&gt;&lt;img alt="SuperTux 1" src="/images/selection-jeux-libres/SuperTux-00.resized.png" /&gt; &lt;img alt="SuperTux 2" src="/images/selection-jeux-libres/SuperTux-01.resized.png" /&gt; &lt;img alt="SuperTux 3" src="/images/selection-jeux-libres/SuperTux-02.resized.png" /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="liens-et-telechargements-2"&gt;
&lt;h3&gt;Liens et téléchargements&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Site officiel : &lt;a class="reference external" href="http://supertux.lethargik.org/"&gt;http://supertux.lethargik.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Téléchargement (Disponible dans les dépôts de la plupart des
distributions) : &lt;a class="reference external" href="http://supertux.lethargik.org/download.html"&gt;http://supertux.lethargik.org/download.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="jeux"></category><category term="libre"></category></entry><entry><title>Mon passage à Pelican</title><link href="https://www.jujens.eu/posts/2014/Apr/22/passage-pelican/" rel="alternate"></link><published>2014-04-22T00:00:00+02:00</published><updated>2014-04-22T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2014-04-22:/posts/2014/Apr/22/passage-pelican/</id><summary type="html">&lt;p class="first last"&gt;Un résumé de mon passage à Pelican, un générateur de sites statiques en python.&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="presentation"&gt;
&lt;h2&gt;Présentation&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://getpelican.com"&gt;Pelican&lt;/a&gt; est un générateur de site statique
écrit en python. Un générateur de site statique est simplement un
programme qui transforme des fichiers textes en un site web avec thème,
catégorie, … Les plus connus sont &lt;a class="reference external" href="http://jekyllrb.com/"&gt;jekyll&lt;/a&gt; qui
est écrit en ruby, &lt;a class="reference external" href="http://middlemanapp.com/"&gt;middleman&lt;/a&gt; en ruby
également, &lt;a class="reference external" href="http://octopress.org/"&gt;octopress&lt;/a&gt; qui est basé sur jekyll
et &lt;a class="reference external" href="http://nanoc.ws/"&gt;nanoc&lt;/a&gt;. Mais il y en a beaucoup d'autres.&lt;/p&gt;
&lt;p&gt;L'avantage de ce genre de site est qu'ils sont simples, qu'ils n'ont pas
besoin de bases de données ou de langages particuliers comme PHP sur
votre serveur. Il faut juste un serveur web qui délivre des fichiers. Et
ça, ils sont tous capables de le faire. J'ajouterais également la
simplicité de rédaction : pas d'éditeur WYSIWYG, juste du texte brut et
une syntaxe légère pour y donner du sens. Cela implique également que
l'on peut utiliser un gestionnaire de version comme git ou mercurial
pour gérer les fichiers (et pour en faire des sauvegardes facilement).&lt;/p&gt;
&lt;p&gt;Avant de passer à Pelican, j'ai pu tester Jekyll. Les deux sont très
proches, très complets et extensibles via des plugins. Si j'ai préféré
Pelican c'est parce que :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Il est en python ce qui me permettra de faire des modifications ou des
plugins au besoin (je ne connais pas suffisament ruby pour ça).&lt;/li&gt;
&lt;li&gt;Il a un système de cache ce qui évite de devoir recompiller tout les
fichiers à chaque fois.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="linstallation"&gt;
&lt;h2&gt;L’installation&lt;/h2&gt;
&lt;p&gt;Tout est très bien détaillé dans
&lt;a class="reference external" href="http://docs.getpelican.com/en/3.3.0/getting_started.html"&gt;la documentation officielle&lt;/a&gt;.
J'ai choisi la méthode recommandé : dans un enviromment virtuel, en
python 3 (pour être moderne) et à partir de la version git (le plugin
des commentaires statiques ne fonctionne pas bien sur la dernière
version publiée).&lt;/p&gt;
&lt;p&gt;Attention toute fois, en python&amp;nbsp;3 la commande &lt;tt class="docutils literal"&gt;virtualenv&lt;/tt&gt; ne
fonctionne pas comme il faut. J'ai utilisé la commande
&lt;tt class="docutils literal"&gt;python3 &lt;span class="pre"&gt;-m&lt;/span&gt; venv blog&lt;/tt&gt; pour créer cet environement dans le dossier
&lt;em&gt;blog&lt;/em&gt;. Le module &lt;em&gt;venv&lt;/em&gt; vient par défaut avec python&amp;nbsp;3. Reste à
installer pelican et toutes les bibliothèques avec
&lt;tt class="docutils literal"&gt;pip install &amp;lt;bibliothèque&amp;gt;&lt;/tt&gt;. Pelican a nottament besoin de Markdown.
Les autres sont installées comme dépendance par pip. Le support
d'Asciidoc demande un peu plus de travail. Allez voir la doc pour plus
d'informations.&lt;/p&gt;
&lt;p&gt;Ensuite, la commande &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pelican-quickstart&lt;/span&gt;&lt;/tt&gt; va vous aider à générer la
configuration du site. Vous allez devoir répondre à plusieurs questions
pour que le site vous corresponde. Point intéressant, cette commande va
aussi créer un Makefile pour vous aider à automatiser le processus de
création de site. Plus d'infos ci-dessous.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-site"&gt;
&lt;h2&gt;Le site&lt;/h2&gt;
&lt;p&gt;Par défaut, Pelican supporte les articles écrits en markdown,
RestructuredText ou dans une moindre mesure Asciidoc. On écrit
simplement l'article dans le dossier &lt;em&gt;content&lt;/em&gt; (si vous avez lancez
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pelican-quickstart&lt;/span&gt;&lt;/tt&gt;) et on l'enregistre avec la bonne extension (.md
pour du markdown ou .rst pour RestructuredText).&lt;/p&gt;
&lt;p&gt;Une fois l'article écrit, on lance la commande
&lt;tt class="docutils literal"&gt;pelican chemin/vers/votre/article&lt;/tt&gt; et pelican va générer votre site
statique dans un dossier nommé output. Si vous avez un Makefile, un
simple &lt;tt class="docutils literal"&gt;make html&lt;/tt&gt; suffira. Pour visualiser votre site sur votre
ordinateur, &lt;tt class="docutils literal"&gt;make serve&lt;/tt&gt; va lancer un serveur sur
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://localhost:8000&lt;/span&gt;&lt;/tt&gt;. Simple et efficace.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-plugins-et-les-themes"&gt;
&lt;h2&gt;Les plugins et les thèmes&lt;/h2&gt;
&lt;p&gt;Un point intéressant : les fonctionnalités de Pelican peuvent être
étendu via des plugins. Cela va de l'ajout d'un système de commentaires
statiques au rendu des équations LaTeX en passant par une gallerie.
Allez par &lt;a class="reference external" href="https://github.com/Scheirle/pelican-plugins"&gt;là&lt;/a&gt; pour en
savoir plus.&lt;/p&gt;
&lt;p&gt;J'ai d'ailleur écrit mon propre plugin : creole-reader qui ajoute le
support de la
&lt;a class="reference external" href="http://en.wikipedia.org/wiki/Creole_%28markup%29"&gt;syntaxe creole&lt;/a&gt;
dans pelican. Je ne suis en effet pas un grand fan de Markdown et la
syntaxe Rest ne me plait que moyennement. Je crérais peut être aussi un
plugin pour org-mode si je n'arrive pas à me faire à Rest (creole est
beaucoup moins complet et trop limité pour des long tutos).&lt;/p&gt;
&lt;p&gt;Si vous pouvez créer facilement des thèmes à partir du moteur de
template Jinja2, vous pouvez aussi prendre un des nombreux thèmes
disponibles &lt;a class="reference external" href="https://github.com/Scheirle/pelican-themes"&gt;ici&lt;/a&gt;. J'ai
choisi dev-random2 (que j'ai un peu adapté).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Mon passage à Pelican est récent mais je suis vraiment séduit par
l'outil. Auparavant, mon blog était en Drupal. Cela peut paraître
étrange, mais je gère une installation multisite de Drupal, donc ce
n'était pas un problème. Par contre, Drupal manque de souplesse pour un
blog. D'autant que sur notre installation, c'est soit du texte brut soit
du HTML (éventuellement avec CKEditor). C'est d'autant plus pénible que
CKeditor supporte très mal le code et à tendance à enlever certaines
balise. Peu pratique, idem pour la gestion des révisions c'est pas
souple même si ça marche très bien.&lt;/p&gt;
&lt;p&gt;Avec pelican, je suis très content de pouvoir utiliser du balisage léger
et mercurial pour les révisions. Plus qu'à importer tout mes articles
;-).&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="pelican"></category><category term="Python"></category></entry><entry><title>Drupal : un an d'utilisation, mes impressions</title><link href="https://www.jujens.eu/posts/2014/Feb/01/drupal-an-utilisation-impressions/" rel="alternate"></link><published>2014-02-01T00:00:00+01:00</published><updated>2014-02-01T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2014-02-01:/posts/2014/Feb/01/drupal-an-utilisation-impressions/</id><summary type="html">&lt;p class="first last"&gt;Je suis utilisateur de &lt;a class="reference external" href="http://drupal.org"&gt;Drupal&lt;/a&gt; depuis environ 1 an maintenant et j’ai donc décidé de partager ce que j’ai appris avec cet outil que j’ai découvert à Centrale Marseille (ECM) grâce au  &lt;a class="reference external" href="http://assos.centrale-marseille.fr"&gt;projet multi-assos&lt;/a&gt;. Aujourd’hui, j’admets avoir pris goût à l’outil et je suis le webmaster du forum foceen un des plus gros sites de l’installation (et un des plus compliqué aussi).&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Je suis utilisateur de &lt;a class="reference external" href="http://drupal.org"&gt;Drupal&lt;/a&gt; depuis environ 1
an maintenant et j’ai donc décidé de partager ce que j’ai appris avec
cet outil que j’ai découvert à Centrale Marseille (ECM) grâce au &lt;a class="reference external" href="http://assos.centrale-marseille.fr"&gt;projet
multi-assos&lt;/a&gt; (qui héberge cet
humble blog). Aujourd’hui, j’admets avoir pris goût à l’outil et je suis
le webmaster du forum foceen un des plus gros sites de l’installation
(et un des plus compliqué aussi).&lt;/p&gt;
&lt;div class="section" id="avant"&gt;
&lt;h2&gt;Avant&lt;/h2&gt;
&lt;p&gt;Avant d’entrer à l’ECM, je n’avais jamais fait de vrai site web. J’avais
appris quelques bases de php et de mysql sur le site du zéro
(&lt;a class="reference external" href="http://fr.openclassrooms.com/"&gt;openclassrooms&lt;/a&gt; maintenant). J’avais
un peu entendu parler de framework et de&amp;nbsp;CMS mais je n’en pensais pas
grand chose de bien. Sans trop caricatirer, je pensais : &lt;em&gt;« Non mais un
site ça se code à la main. Les CMS et les framework c’est des machins
d’assistés&amp;nbsp;»&lt;/em&gt;. Et puis j'ai découvert Drupal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="drupal-1"&gt;
&lt;h2&gt;Drupal&lt;/h2&gt;
&lt;p&gt;Si vous ne connaissez pas encore &lt;a class="reference external" href="http://drupal.org"&gt;Drupal&lt;/a&gt;,
permettez que je vous présente l'outil. C'est un des CMS les plus
utilisés et les plus populaires. Drupal accueille de gros sites (comme
celui de la maison blanche) et des plus modestes (comme celui-ci) et
globalement de nombreux sites d'entreprises ou d'institutions.&lt;/p&gt;
&lt;p&gt;Un des gros avantages de Drupal est sa modularité. Sa communauté très
active maintient de très nombreux modules de qualité qui vous permettent
de transformer votre Drupal en site de e-commerce, en forum ou plus
simplement de lui ajouter des fonctionnalités intéressantes comme un
éditeur WYSIWYG. Le tout sans coder une seule ligne. De plus, ces
modules viennent avec des interfaces puissantes mais bien faîtes pour
les configurer suivant vos besoins. Le tout juste avec votre souris. Ce
qui signifie que vous n'avez pas besoin d'être un geek pour être
webmaster d'un site. Tout ce fait très bien via l'interface (sauf pour
des besoins très spécifiques qui demandent du dévelloppement).&lt;/p&gt;
&lt;p&gt;La communauté crée aussi pleins de thèmes et certains sont mêmes
adaptatifs. Pas besoin d'être un très bon designer pour avoir un joli
site. Plutôt cool non ?&lt;/p&gt;
&lt;p&gt;Ce sont toutes ces raisons qui font que Drupal est bien adapté pour les
associations de l'école. On est pas des dévelopeurs et cela permet de
péréniser les sites web. Car c'est dur de faire un site web (du moins
correctement).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="administrer-l-installation"&gt;
&lt;h2&gt;Administrer l'installation&lt;/h2&gt;
&lt;p&gt;En tant que membre du club Drupal, je fais des sites web (et aide ceux
qui en font) et administre l'installation. C'est à mon avis la partie la
plus intéressante.&lt;/p&gt;
&lt;p&gt;Faire des scripts Shell pour automatiser des tâches, faire du git,
monter un serveur en local, bosser sur les migartions de drupal,… c'est
intéressant et formateur (surtout quand il ne faut rien casser).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="c-est-dur-de-faire-un-site-web"&gt;
&lt;h2&gt;C'est dur de faire un site web&lt;/h2&gt;
&lt;p&gt;Contrairement à ce que peut laisser penser un cours de PHP ce n'est pas
simple de faire un site web correctement. Ma (certes assez petite)
expérience de webmaster me l'a bien montrée. Sur un site fait par
quelqu'un qui est reconnu comme plutôt bon et qui sait coder, il peut y
avoir de nombreux problèmes.&lt;/p&gt;
&lt;p&gt;Ceux que j'ai vu et qui m'ont posés le plus de problèmes sont :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;du code non indenté&lt;/li&gt;
&lt;li&gt;les mots de passe stockés en clair dans la base de données (si si, il
y en a qui le font)&lt;/li&gt;
&lt;li&gt;des accès base de données sans passer par PDO (donc pas de requêtes
préparées et des injections SQL faciles à faire)&lt;/li&gt;
&lt;li&gt;des données encodées n'importe comment dans une base de données&lt;/li&gt;
&lt;li&gt;fonction_1, fonction_2 et fonction_3 qui font la même chose (en
tout cas je n'ai pas vu la différence). Et la gestion de version ça
sert à quoi ?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Drupal c'est bien ;-). Vous vous prendrez la tête sur l'interface au
lieu de vous prendre la tête sur du code mais bon, vous surviverez. Vous
tomberez aussi sur des gens qui pense que comme tout se fait à la souris
c'est facile et ça ne s'apprend pas. Ça c'est faux. Il vont faire des
pages entièrement codé en html sans utiliser les champs et les vues. Ils
vous dirons que Drupal c'est pourri et qu'en PHP ils auraient pu le
faire facilement. On ne peut pas tout résoudre avec un outil, aussi bien
soit-il.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="Drupal"></category></entry></feed>