add kokada.dev (RSS and Atom) - sfeed_tests - sfeed tests and RSS and Atom files
 (HTM) git clone git://git.codemadness.org/sfeed_tests
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 627538879a243435de10999fae80626ea58eea86
 (DIR) parent 0952a65777ea4fa93533cc27ff2c348a61f7c929
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Fri,  6 Dec 2024 13:04:24 +0100
       
       add kokada.dev (RSS and Atom)
       
       uses https://capivaras.dev/ (blogging platform)
       
       Diffstat:
         A input/sfeed/realworld/kokada.dev.a… |    3703 +++++++++++++++++++++++++++++++
         A input/sfeed/realworld/kokada.dev.r… |    3707 +++++++++++++++++++++++++++++++
       
       2 files changed, 7410 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/input/sfeed/realworld/kokada.dev.atom.xml b/input/sfeed/realworld/kokada.dev.atom.xml
       @@ -0,0 +1,3702 @@
       +<?xml version="1.0" encoding="utf-8"?>
       +<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>kokada</title><link href="https://kokada.dev" rel="alternate"/><link href="https://kokada.dev/atom/" rel="self"/><id>https://kokada.dev</id><updated>2024-12-04T00:00:00+00:00</updated><subtitle/><entry><title>Praise to scdoc to generate man pages</title><link href="https://kokada.dev/blog/praise-to-scdoc-to-generate-man-pages/" rel="alternate"/><published>2024-12-04T00:00:00+00:00</published><id>https://kokada.dev/blog/praise-to-scdoc-to-generate-man-pages/</id><summary type="html">&lt;p&gt;Hey, its been a long time since my &lt;a href="posts/2024-10-07/01-enabling-le-audio-lc3-in-wf-1000xm5.md"&gt;last blog
       +post&lt;/a&gt;. It is mostly
       +because I ran out of things to write, but I expected this. This is probably
       +more likely how I am actually going to post from now. At least, it shows that
       +my plan to have a &lt;a href="https://kokada.dev/blog/making-a-blog-for-the-next-10-years"&gt;blog for a long
       +time&lt;/a&gt;, that is
       +easy to go back when I wanted is working fine, but I digress.&lt;/p&gt;
       +&lt;p&gt;Going back to the theme of the today blog post, I needed to write a &lt;a href="https://en.wikipedia.org/wiki/Man_page"&gt;man
       +page&lt;/a&gt; for the first time in years. I
       +hate &lt;a href="https://en.wikipedia.org/wiki/Troff"&gt;troff&lt;/a&gt;, the typesetting system used
       +for man pages (similar to &lt;a href="https://en.wikipedia.org/wiki/LaTeX"&gt;LaTeX&lt;/a&gt; for
       +documents). It is one of the weirdest languages that I ever saw, and even the
       +example in Wikipedia shows that:&lt;/p&gt;
       +&lt;pre&gt;&lt;code class="language-troff"&gt;.ND &amp;quot;January 10, 1993&amp;quot;
       +.AU &amp;quot;Ms. Jane Smith&amp;quot;
       +.AT &amp;quot;Upcoming appointment&amp;quot;
       +.MT 5
       +.DS
       +Reference #A12345
       +.sp 4
       +Mr. Samuel Jones
       +Field director, Bureau of Inspections
       +1010 Government Plaza
       +Capitoltown, ST
       +.sp 3
       +Dear Mr. Jones,
       +.sp 2
       +.P
       +Making reference to the noted obligation to submit for state inspection our newly created production process, we request that you consider the possible inappropriateness of subjecting the  innovative technologies of tomorrow to the largely antiquated requirements of yesterday.  If our great state is to prosper in the twenty-first century, we must take steps
       +.B now ,
       +in
       +.I this
       +year of
       +.I this
       +decade, to prepare our industrial base for the interstate and international competition that is sure to appear.  Our new process does precisely that.  Please do not let it be undone by a regulatory environment that is no longer apt.
       +.P
       +Thank you for your consideration of our position.
       +.FC Sincerely
       +.SG
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Keep in mind that the break lines are necessary every time you introduce a
       +macro, like &lt;code&gt;.I this&lt;/code&gt; (that I &lt;em&gt;think&lt;/em&gt; it is for italics). Yes, this format is
       +as illegible as hell, and it is worse that the format lacks good tooling (or at
       +least I didn't find any good ones).&lt;/p&gt;
       +&lt;p&gt;Most people when they need to write a man page nowadays ends up using some
       +other format that generates a man page. For example, in the past I used
       +&lt;a href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt; to convert Markdown to a man page, but even if
       +Pandoc is a great project the result is sub-optimal at best: Markdowns are, at
       +the end, designed for generating HTML (and a subset of it), and not man pages,
       +so you basically ends up fighting the format for it to do what you want.
       +Also, Pandoc is a big project, with a ~200MB binary (at least it is the default
       +Pandoc binary in Nix).&lt;/p&gt;
       +&lt;p&gt;For this specific project I needed something small. I am trying to replace one
       +of the most essential pieces inside NixOS, &lt;code&gt;nixos-rebuild&lt;/code&gt;, written in Bash,
       +with a &lt;a href="https://discourse.nixos.org/t/nixos-rebuild-ng-a-nixos-rebuild-rewrite/55606/"&gt;full rewritten in
       +Python&lt;/a&gt;
       +(sorry Rust zealots!), called &lt;code&gt;nixos-rebuild-ng&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;Since this project will eventually (if successful) be in the critical path for
       +NixOS, I want to reduce the number of dependencies as much as possible, so
       +something as big as Pandoc is out. I could use
       +&lt;a href="https://asciidoc.org/"&gt;AsciiDoc&lt;/a&gt;, but it is a big complicated Python project
       +(this may seem ironic, but &lt;code&gt;nixos-rebuild-ng&lt;/code&gt; has only one runtime dependency,
       +that is optional). And I also hated the last time I tried to use it to generate
       +man pages: it more flexible than Markdown, but still far from optimal.&lt;/p&gt;
       +&lt;p&gt;Thanks to Drew DeVault (creator of &lt;a href="https://swaywm.org/"&gt;SwayWM&lt;/a&gt;) that seems it
       +had the same issues in the past and created
       +&lt;a href="https://drewdevault.com/2018/05/13/scdoc.html"&gt;&lt;code&gt;scdoc&lt;/code&gt;&lt;/a&gt;, a very simple man
       +page generator using a DSL inspired in Markdown, but specific to generate man
       +pages. The binary is written in C (and advantage in this case since it means it
       +is easier to bootstrap), is small (~1 Kloc) and has no dependencies, so it
       +fits the requirement.&lt;/p&gt;
       +&lt;p&gt;While the language suffers from being a niche project for a niche segment, the
       +&lt;a href="https://man.archlinux.org/man/scdoc.5.en"&gt;man page&lt;/a&gt; for it is actually really
       +nice. It is terse though and lacks examples, and this is what this blog post
       +will try to accomplish.&lt;/p&gt;
       +&lt;p&gt;To start, let's have a quick summary of the syntax, written in &lt;code&gt;scdoc&lt;/code&gt; as
       +comments:&lt;/p&gt;
       +&lt;pre&gt;&lt;code class="language-scdoc"&gt;; quick summary:
       +; # new section
       +; comments starts with ;
       +; - this is a list
       +;         - sub-list
       +; - *bold*: _underline_, force a line break++
       +; - [tables], \[ can be used to force an actual [
       +; . numbered list
       +; please configure your editor to use hard tabs
       +; see `man 5 scdoc` for more information about syntax
       +; or https://man.archlinux.org/man/scdoc.5.en
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;I actually added this summary in the &lt;code&gt;.scd&lt;/code&gt; (the &lt;code&gt;scdoc&lt;/code&gt; extension) files that
       +I wrote, so it is easy for someone that never saw the format to start
       +collaborating.&lt;/p&gt;
       +&lt;p&gt;And here an example of a (summarised) man page in &lt;code&gt;.scd&lt;/code&gt; format:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nixos-rebuild-ng(8)
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# NAME
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nixos-rebuild - reconfigure a NixOS machine
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# SYNOPSIS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;_nixos-rebuild_&lt;/span&gt; \[--upgrade] [--upgrade-all]++
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                \[{switch,boot}]
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# DESCRIPTION
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This command has one required argument, which specifies the desired operation.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It must be one of the following:
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;*switch*&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Build and activate the new configuration, and make it the boot default.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        That is, the configuration is added to the GRUB boot menu as the
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        default menu entry, so that subsequent reboots will boot the system
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        into the new configuration. Previous configurations activated with
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        nixos-rebuild switch or nixos-rebuild boot remain available in the GRUB
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        menu.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;*boot*&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Build the new configuration and make it the boot default (as with
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ge"&gt;*nixos-rebuild switch*&lt;/span&gt;), but do not activate it. That is, the system
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        continues to run the previous configuration until the next reboot.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# OPTIONS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;*--upgrade, --upgrade-all*&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Update the root user&amp;#39;s channel named &amp;#39;nixos&amp;#39; before rebuilding the
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        system.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        In addition to the &amp;#39;nixos&amp;#39; channel, the root user&amp;#39;s channels which have
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        a file named &amp;#39;.update-on-nixos-rebuild&amp;#39; in their base directory will
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        also be updated.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Passing &lt;span class="ge"&gt;*--upgrade-all*&lt;/span&gt; updates all of the root user&amp;#39;s channels.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;See the Nix manual, &lt;span class="ge"&gt;*nix flake lock --help*&lt;/span&gt; or &lt;span class="ge"&gt;*nix-build --help*&lt;/span&gt; for details.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# ENVIRONMENT
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NIXOS_CONFIG
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Path to the main NixOS configuration module. Defaults to
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ge"&gt;_/etc/nixos/configuration.nix_&lt;/span&gt;.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# FILES
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/etc/nixos/flake.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        If this file exists, then &lt;span class="ge"&gt;*nixos-rebuild*&lt;/span&gt; will use it as if the
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ge"&gt;*--flake*&lt;/span&gt; option was given. This file may be a symlink to a
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        flake.nix in an actual flake; thus &lt;span class="ge"&gt;_/etc/nixos_&lt;/span&gt; need not be a
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        flake.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# AUTHORS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Nixpkgs/NixOS contributors
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is a screenshot of the result:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-12-04/2024-12-04-230955_hyprshot.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-12-04/2024-12-04-230955_hyprshot.png" alt="Man page rendered from scd
       +file"&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;One of nice things that I found is how looking at the plain text looks kind
       +like the man page result already. And if you know Markdown, you can basically
       +understand most things that is happening. There are a few differences, like
       +&lt;code&gt;*bold*&lt;/code&gt; instead of &lt;code&gt;**bold**&lt;/code&gt;, and while they're unfortunate they're not the
       +end of the world.&lt;/p&gt;
       +&lt;p&gt;Now, the format has its quirks. The first line being the name of the program
       +and section in parenthesis is required, but this makes sense, since you need
       +this information for the corners. But for one, it requires the usage of hard
       +tabs to create indentation, and the error messages are awful, in a situation
       +that kind remembers me of &lt;code&gt;Makefile&lt;/code&gt;. Also the choice of &lt;code&gt;[&lt;/code&gt; to start a table
       +means that the traditional &lt;code&gt;app [command]&lt;/code&gt; needs in many cases to be escaped as
       +&lt;code&gt;app \[command]&lt;/code&gt;. I found this a strange choice since this is supposed to be a
       +format that is only used for man pages, and using &lt;code&gt;[command]&lt;/code&gt; to indicate an
       +optional is common, but at least it is easy to escape.&lt;/p&gt;
       +&lt;p&gt;In the end, I think all that matters is the result. And for the first time for
       +all those years trying to write a man page, I am satisfied with the result. The
       +man page looks exactly as I wanted once rendered, and the &lt;code&gt;.scd&lt;/code&gt; file looks
       +reasonable good that it can work as a documentation for someone that for one
       +reason or another can't use the man page (can't say the same for the troff
       +version). Also, it is really easy for someone to just go there and update the
       +man page, even without experience in the format (except for maybe the
       +requirement of tabs). So all in all, I really liked the format, and will use it
       +again if I need to write another man page in the future.&lt;/p&gt;</summary></entry><entry><title>Enabling LE Audio/LC3 in WF-1000XM5</title><link href="https://kokada.dev/blog/enabling-le-audio-lc3-in-wf-1000xm5/" rel="alternate"/><published>2024-10-07T00:00:00+00:00</published><id>https://kokada.dev/blog/enabling-le-audio-lc3-in-wf-1000xm5/</id><summary type="html">&lt;p&gt;One of things that I hate the most about the fact that we are all using
       +wireless earbuds instead of wired earphones is the latency: it is bad, getting
       +up to seconds(!) depending on your particular combination of OS/earbuds/device.&lt;/p&gt;
       +&lt;p&gt;There is a solution though: Bluetooth LE Audio, that is supposed to fix
       +multiple issues with the original design for Bluetooth Classic Audio, including
       +a much lower latency, improved efficiency (e.g.: less battery power) and even
       +multiple streams of audio. LE Audio also includes a new default codec for
       +improved audio quality, &lt;a href="https://en.wikipedia.org/wiki/LC3_(codec)"&gt;LC3&lt;/a&gt;, that
       +replaces the venerable &lt;a href="https://en.wikipedia.org/wiki/SBC_(codec)"&gt;SBC&lt;/a&gt; codec
       +for audio.&lt;/p&gt;
       +&lt;p&gt;However, the standard is a mess right now: a few wireless headphones already
       +support it, but they're generally disabled by default and it is pretty messy to
       +enable. And even after enabling it, getting it to work can be a pain.&lt;/p&gt;
       +&lt;p&gt;I have pretty much the best setup to use LE Audio right now: a recently
       +released Pixel 9 Pro with Sony's
       +&lt;a href="https://www.sony.ie/headphones/products/wf-1000xm5"&gt;WF-1000XM5&lt;/a&gt; earbuds, and
       +after lots of tries I got it to work. You can see below the versions of
       +everything I am using:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;Android: 14&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.sony.songpal.mdr"&gt;Sound
       +Connect&lt;/a&gt;:
       +11.0.1&lt;/li&gt;
       +&lt;li&gt;WM-1000XM5: 4.0.2&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;The first thing you need to do is enable in &amp;quot;Sound Connect&amp;quot; app &amp;quot;LE Audio
       +Priority&amp;quot; in &amp;quot;Device Settings -&amp;gt; System&amp;quot;:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642922_y.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642922_y.jpg" alt="LE Audio option inside Sound
       +Connect"&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;After this, you will need to pair your headset with the device again. You can
       +do this as same as always: press and hold the button in case for a few seconds
       +until a blue light starts to blink. However, this is where things starts to get
       +janky: I couldn't get the headset to pair with Android again.&lt;/p&gt;
       +&lt;p&gt;A few of the things that I needed to do (in no specific order):&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;Remove the previous paired headset&lt;/li&gt;
       +&lt;li&gt;Restart the Android&lt;/li&gt;
       +&lt;li&gt;Clean-up &amp;quot;Sound Connect&amp;quot; storage (Long press the app icon -&amp;gt; &amp;quot;App info&amp;quot; -&amp;gt;
       +&amp;quot;Storage and Cache&amp;quot; -&amp;gt; &amp;quot;Clear storage&amp;quot;)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;If you can get the headset to connect, go to the &amp;quot;Bluetooth&amp;quot; settings in
       +Android, click in the gear icon for the headset and enable &amp;quot;LE Audio&amp;quot; option:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642937_y.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642937_y.jpg" alt="LE Audio option Bluetooth
       +Settings"&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;If you can't, you may want to &lt;a href="https://helpguide.sony.net/mdr/2963/v1/en/contents/TP1000783925.html"&gt;restore the headset to factory
       +settings&lt;/a&gt;
       +and try again from the start (that means pairing your device with &amp;quot;Sound
       +Connect&amp;quot; again, and you may want to try to clear the storage before doing so).&lt;/p&gt;
       +&lt;p&gt;Yes, the process is extremely janky, but I think this is why both &amp;quot;Sound
       +Connect&amp;quot; and Android marks this feature as beta/experimental. And I still need
       +to test the latency, but from my initial testing there are some glitches when
       +the audio is only used for a short period of time (e.g.: Duolingo only enables
       +the audio when the character is speaking). So I only recommend this if you want
       +to test how LE Audio will behave, since it is clear that this needs more
       +polish.&lt;/p&gt;</summary></entry><entry><title>Quick bits: where does Nix store Flake's trusted settings?</title><link href="https://kokada.dev/blog/quick-bits-where-does-nix-store-flakes-trusted-settings/" rel="alternate"/><published>2024-09-30T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-where-does-nix-store-flakes-trusted-settings/</id><summary type="html">&lt;p&gt;Have you ever run a &lt;code&gt;nix build&lt;/code&gt; command and had this prompt?&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix run .#darwinActivations/Sekai-MacBook-Pro
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;do you want to allow configuration setting &amp;#39;extra-substituters&amp;#39; to be set to &amp;#39;https://nix-community.cachix.org https://thiagokokada-nix-configs.cachix.org&amp;#39; (y/N)? y
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;do you want to permanently mark this value as trusted (y/N)? y
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And realise that you did/didn't want to mark this value as trusted? But where
       +is this stored? Well, I had to look at the Nix source code to find the answer,
       +since I can't find this anywhere in the web or in ChatGPT (but I am sure that
       +now that I posted this it will eventually be searchable), and the answer can be
       +found
       +&lt;a href="https://github.com/NixOS/nix/blob/c116030605bf7fecd232d0ff3b6fe066f23e4620/src/libflake/flake/config.cc#L13-L16"&gt;here&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="nf"&gt;trustedListPath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getDataDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/trusted-settings.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Where is &lt;code&gt;getDataDir()&lt;/code&gt; though? I found the answer
       +&lt;a href="https://github.com/NixOS/nix/blob/c116030605bf7fecd232d0ff3b6fe066f23e4620/src/libutil/users.cc#L52-L65"&gt;here&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="nf"&gt;getDataDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;NIX_DATA_HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;xdgDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_DATA_HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xdgDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xdgDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getHome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/.local/share/nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we solved the mystery:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;If &lt;code&gt;NIX_DATA_HOME&lt;/code&gt; is set, the file will be in
       +&lt;code&gt;$NIX_DATA_HOME/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
       +&lt;li&gt;If &lt;code&gt;XDG_DATA_HOME&lt;/code&gt; is set, the file will be in
       +&lt;code&gt;$XDG_DATA_HOME/nix/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
       +&lt;li&gt;Otherwise Nix will fallback to &lt;code&gt;$HOME/.local/share/nix/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;By the way, if you don't know why you got this prompt, if &lt;code&gt;flake.nix&lt;/code&gt; has a
       +&lt;code&gt;nixConfig&lt;/code&gt; attribute inside &lt;code&gt;outputs&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; this &lt;code&gt;nixConfig&lt;/code&gt; is an unsafe
       +attribute (like &lt;code&gt;extra-substituters&lt;/code&gt;) you will get this prompt, unless you set
       +&lt;a href="https://nix.dev/manual/nix/2.23/command-ref/conf-file#conf-accept-flake-config"&gt;&lt;code&gt;accept-flake-config = true&lt;/code&gt;&lt;/a&gt;
       +in your Nix configuration (please &lt;strong&gt;do not do this&lt;/strong&gt;, it is dangerous because
       +it may allow running possible unsafe options without asking you first).&lt;/p&gt;
       +&lt;p&gt;You can inspect the JSON file or delete it and Nix will prompt you again the
       +next time you run a &lt;code&gt;nix&lt;/code&gt; command. And yes, saving this preference is
       +controversial considering this is Nix, but having the power to set &lt;code&gt;nixConfig&lt;/code&gt;
       +attributes is really powerful, and with great powers comes great
       +responsibilities.&lt;/p&gt;</summary></entry><entry><title>My coding experience with LLM</title><link href="https://kokada.dev/blog/my-coding-experience-with-llm/" rel="alternate"/><published>2024-09-02T00:00:00+00:00</published><id>https://kokada.dev/blog/my-coding-experience-with-llm/</id><summary type="html">&lt;p&gt;I don't want to talk about AI. AI fatigue is real, with (garbage) AI content
       +everywhere you go. However, I like to talk about tools, and AIs, or more
       +specifically, &lt;a href="https://en.wikipedia.org/wiki/Large_language_model"&gt;Large Language
       +Models&lt;/a&gt; (LLM), can be
       +really useful tools. Maybe someday we will have our AI overlords dominating
       +everything (including my job), but right now I don't feel particularly
       +threatened given my experience with AIs.&lt;/p&gt;
       +&lt;p&gt;Given that &lt;a href="https://mashable.com/article/google-search-low-quality-research"&gt;Google Search is getting
       +worse&lt;/a&gt;, I am
       +slowly relying in LLMs more and more. This is the focus of this post, basically
       +&lt;a href="https://www.rottentomatoes.com/m/the_good_the_bad_and_the_ugly"&gt;the good, the bad and the
       +ugly&lt;/a&gt; (by the
       +way, never saw this film) of LLMs, especially focused for solving coding
       +problems. I will take a look at the my chat history in
       +&lt;a href="https://chatgpt.com/"&gt;ChatGPT&lt;/a&gt; and see where it was a good tool, where it
       +shined, and where it just completely wasted my time.&lt;/p&gt;
       +&lt;p&gt;A disclaimer, I never bothered to buy a ChatGPT subscription because I don't
       +feel that the tool is useful enough (at least yet), so most of the examples
       +here are either with &lt;a href="https://openai.com/index/chatgpt/"&gt;GPT-3.5&lt;/a&gt; or &lt;a href="https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/"&gt;GPT-4o
       +mini&lt;/a&gt;,
       +since those where the models offered by free in ChatGPT.&lt;/p&gt;
       +&lt;p&gt;An addendum, I have some experience with &lt;a href="https://github.com/features/copilot"&gt;GitHub
       +Copilot&lt;/a&gt; (another LLM, but this one runs
       +inside your IDE or favorite text editor) in &lt;code&gt;$PREVIOUS_JOB&lt;/code&gt;. But my experience
       +with it is even more underwhelming than what I am going to describe in this
       +post: it was basically really good for generating fixture for tests and between
       +ok-ish to plain bad for everything else. Yes, that's would fit in a tweet (or
       +xeet?).&lt;/p&gt;
       +&lt;h2&gt;The good&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/c58a1432-2761-4954-acac-cade29942c34"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Is there any way that I can pass a context for a net.conn to cancel its
       +Read()?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This is one of the good cases where ChatGPT felt to me that I was talking with
       +an expert in Go. To start, I didn't even mention in my initial prompt that I
       +was talking about Go, but it correctly inferred so. Also, the solution overview
       +was great:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;ol&gt;
       +&lt;li&gt;&lt;strong&gt;Use a separate goroutine&lt;/strong&gt; to handle the context cancellation.&lt;/li&gt;
       +&lt;li&gt;&lt;strong&gt;Set a deadline&lt;/strong&gt; on the connection using &lt;code&gt;SetReadDeadline()&lt;/code&gt; to unblock
       +the &lt;code&gt;Read()&lt;/code&gt; when the context is canceled.&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Before asking ChatGPT about this issue I had already coded my own naive
       +solution that was something similar to this:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Compared this solution with the one from ChatGPT:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// Start a goroutine to perform the read
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;// Context was canceled or timed out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="c1"&gt;// Wait for the read to finish
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read finished successfully
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The ChatGPT version is much better because:&lt;/p&gt;
       +&lt;ol&gt;
       +&lt;li&gt;It doesn't close the connection socket allowing it to be reused&lt;/li&gt;
       +&lt;li&gt;It waits until the goroutine is finished to avoid leaks&lt;/li&gt;
       +&lt;li&gt;It closes the channel after it finishes&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;p&gt;Now, I don't claim to be a Go expert (far from it), but this was one of those
       +cases where I felt that I was being guided by someone that is coding in Go for
       +years.&lt;/p&gt;
       +&lt;p&gt;But it didn't end there, I asked:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;If I want to reuse the connection, do I need to reset the SetReadDeadline?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Since in this particular case I wanted the underlying connection to be reused,
       +and ChatGPT correctly amended the code:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// Same as before
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;// Context was canceled or timed out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="c1"&gt;// Wait for the read to finish
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Reset the deadline so the connection can be reused
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read finished successfully
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also the explanation on why this happens was good:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;When you set a read deadline with &lt;code&gt;conn.SetReadDeadline(time.Now())&lt;/code&gt; to unblock
       +the &lt;code&gt;Read()&lt;/code&gt; call, this deadline remains in effect for future &lt;code&gt;Read()&lt;/code&gt;
       +operations. This means that if you attempt another read operation after this,
       +it could immediately fail with a timeout error if the deadline is still in
       +effect.
       +To reuse the connection, you'll need to remove the deadline or set a new one.
       +You can do this by calling &lt;code&gt;conn.SetReadDeadline(time.Time{})&lt;/code&gt;, which clears
       +any existing deadline.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;I had one last question that I kind already knew the answer:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Do we need to defer the clearing?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;And ChatGPT correctly returned:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Whether you need to defer the clearing of the read deadline depends on how
       +your code is structured. If you want to ensure that the read deadline is
       +always cleared after a context cancellation, even if the code panics or exits
       +early, then deferring the &lt;code&gt;conn.SetReadDeadline(time.Time{})&lt;/code&gt; call is a good
       +idea.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This whole interaction was one of the best ones I ever had in ChatGPT, one that
       +it gave me an actual functional code with good explanation on what was
       +happening. But still, it didn't talked at all about the gotcha of setting the
       +read deadline and reusing the underlying connection, needing another prompt to
       +give me this information.&lt;/p&gt;
       +&lt;p&gt;Also, there is one issue in ChatGPT solution though: we are discarding the
       +values of the buffer once the context is done by returning &lt;code&gt;n = 0&lt;/code&gt; instead of
       +the number of bytes read. In this particular case this is what I wanted, but
       +this is a possible issue that ChatGPT didn't talk.&lt;/p&gt;
       +&lt;p&gt;For comparison,
       +&lt;a href="https://stackoverflow.com/questions/61637359/how-to-use-context-context-with-tcp-connection-read"&gt;this&lt;/a&gt;
       +is the best that I got searching in both DuckDuckGo and Google, that is where I
       +kind based my first solution on. I still not sure where ChatGPT could give me
       +such a good answer for this question, because I just couldn't find anything
       +remotely similar during my searches.&lt;/p&gt;
       +&lt;h2&gt;The amazing&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/0c9a148f-4cfd-4b4e-aa84-7486e185125b"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;I want to write an extension to &lt;a href="https://github.com/yuin/goldmark/"&gt;https://github.com/yuin/goldmark/&lt;/a&gt; that allows
       +me to rewrite &lt;code&gt;[link](../2024-07-30/post.md)&lt;/code&gt; to
       +&lt;code&gt;[link](https://kokada.capivaras.dev/slug)&lt;/code&gt;&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Here is the generated code:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;linkrewriter&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;strings&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/ast&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/parser&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/renderer&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/util&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// LinkRewriter is the main struct for your extension
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LinkRewriter&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// NewLinkRewriter returns a new instance of LinkRewriter
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;NewLinkRewriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Extend will be called by Goldmark to add your extension
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;Extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AddOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithASTTransformers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Prioritized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Transform is the method that modifies the AST
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entering&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WalkStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entering&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WalkContinue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="nf"&gt;rewriteLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WalkContinue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// rewriteLink modifies the link URL
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;rewriteLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;../&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Modify this part according to your slug generation logic
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nf"&gt;generateSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://kokada.capivaras.dev/%s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// generateSlug generates a slug from the given URL
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;generateSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Assuming the slug is the last part of the URL without the extension
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/renderer/html&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;linkrewriter&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;path/to/your/linkrewriter&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;md&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithExtensions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="nx"&gt;linkrewriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewLinkRewriter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithRendererOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHardWraps&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Use `md.Convert` to convert your markdown to HTML as usual
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This one will need some context. &lt;a href="https://github.com/yuin/goldmark/"&gt;Goldmark&lt;/a&gt;,
       +the Markdown renderer that &lt;a href="https://kokada.dev/blog/making-a-blog-for-the-next-10-years"&gt;powers this
       +blog&lt;/a&gt;, does not
       +have good examples or documentation (at least I didn't found neither).&lt;/p&gt;
       +&lt;p&gt;So I was not really hopeful I would get any answers in ChatGPT, but not only it
       +put me in the correct path, the code was almost correct. It did had some unused
       +and missing imports, and there were some compilation errors that were easily
       +fixed. And when it registered the extension, it also added a
       +&lt;code&gt;html.WithHardWraps()&lt;/code&gt; renderer for no reason at all.&lt;/p&gt;
       +&lt;p&gt;But this is one of the experiences in LLM that most impressed me. I had no idea
       +at all on how to solve this problem, and ChatGPT basically gave me the
       +solution. A &lt;a href="https://github.com/thiagokokada/blog/blob/3a2ea58a0638d1005617d25eabeae0626933db46/link_rewriter.go"&gt;modified version of the
       +code&lt;/a&gt;
       +from this chat is still used to power this blog to this day, that also
       +demonstrates that the code is not garbage (since now I understand what it does
       +and I can even extend it).&lt;/p&gt;
       +&lt;p&gt;I am still not sure how the license situation of this code is though. Does
       +licenses even make sense in AI generated code?&lt;/p&gt;
       +&lt;h2&gt;The bad&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/95fcd777-b03c-4cfe-8d1d-12e53f5a4dfe"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;I want to configure VM options inside IntelliJ by project&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;My project uses sbt, and I want to set this in a way that I can share with my
       +colleagues&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;I am using a build.sbt file, but IntelliJ is not loading it's JVM options and
       +I need to set this up manually. I want to automate this&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This is an example of my most common interaction with ChatGPT: it generally
       +gives some options that could work, but not in my specific case. So I start to
       +ask for more information until it eventually starts to
       +&lt;a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)"&gt;hallucinate&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;In this particular case, the hallucination was in:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;The sbt-idea-plugin can generate IntelliJ IDEA project files based on your
       +SBT configuration, including JVM options. This allows you to ensure that
       +IntelliJ loads the correct settings from the SBT configuration.&lt;/p&gt;
       +&lt;p&gt;Steps:&lt;/p&gt;
       +&lt;ol&gt;
       +&lt;li&gt;Add the &lt;code&gt;sbt-idea-plugin&lt;/code&gt; to your &lt;code&gt;project/plugins.sbt&lt;/code&gt; file:&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;addSbtPlugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;com.github.mwz&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;sbt-idea&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;2.4.0&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
       +&lt;li&gt;Run the following command in your terminal to regenerate IntelliJ IDEA
       +project files:&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sbt gen-idea
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
       +&lt;p&gt;Yes, there is a
       +&lt;a href="https://github.com/JetBrains/sbt-idea-plugin"&gt;&lt;code&gt;sbt-idea-plugin&lt;/code&gt;&lt;/a&gt;, but no, it
       +doesn't generate project files based in my SBT configuration. It is not even
       +from the repository that it is supposed to be pointing too.&lt;/p&gt;
       +&lt;p&gt;I will just not say that this interaction was a waste of time because some of
       +the other ideas can be applied, but this was the kind of case where I ended up
       +just being disappointed because the thing that seemed to be a possible solution
       +for my problem ended up not existing at all.&lt;/p&gt;
       +&lt;h2&gt;The ugly&lt;/h2&gt;
       +&lt;p&gt;There are lots of examples where ChatGPT just plain disappointed me. Here is a
       +few:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;What is the language that is a fork of Go that has extra features like map?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/2b854bbd-d376-4049-97fe-b47321c1ef07"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;This is one of those cases where I saw something once and was trying to
       +remember. ChatGPT just gave me lots of answers that were not a fork of Go
       +(&lt;a href="https://vlang.io/"&gt;V&lt;/a&gt;, &lt;a href="https://github.com/gluon-lang/gluon"&gt;Gluon&lt;/a&gt;,
       +&lt;a href="https://www.ponylang.io"&gt;Pony&lt;/a&gt;), hallucinated a programming language (Gomini),
       +and said that &lt;a href="https://ziglang.org/"&gt;Zig&lt;/a&gt; is focused in STEM.&lt;/p&gt;
       +&lt;p&gt;By the way, I eventually remember what the language was:
       +&lt;a href="https://goplus.org/"&gt;Go+&lt;/a&gt;. To be fair with ChatGPT, I misremembered that the
       +Go+ had map (as the &lt;a href="https://en.wikipedia.org/wiki/Map_(higher-order_function)"&gt;high-order
       +function&lt;/a&gt;, not a
       +hashmap that both Go and Go+ have). But still, Go+ explicit announces that it
       +is a language for STEM everywhere in their website, and even them ChatGPT
       +couldn't answer.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;In IntelliJ, I need to pass some VM options to the run configuration for my
       +application to run tests, e.g.:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;--add-exports=java.base/sun.security.x509=ALL-UNNAMED
       +--add-opens=java.base/sun.security.ssl=ALL-UNNAMED
       +-Dconfig.resource=api.test.conf
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;However, the &lt;code&gt;api.test.conf&lt;/code&gt; may be &lt;code&gt;jobs.api.conf&lt;/code&gt; or something else
       +depending on the subproject I am running inside Idea. Can I parametrize this
       +in some way?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/aa9bd051-65de-49d9-8d56-0e28de94722c"&gt;Chat history&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;So quick answer: you can't. IntelliJ macros are not powerful enough for this.
       +ChatGPT itself said so when I asked about the IntelliJ macros
       +&lt;a href="https://chatgpt.com/share/7a057bf5-bcec-458b-9432-98e3f551ad28"&gt;directly&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;But in this particular case, ChatGPT happily said &amp;quot;yes, you can parameterize VM
       +options in IntelliJ IDEA to make them more flexible depending on the subproject
       +or other context&amp;quot;. Thanks for wasting my time, I guess.&lt;/p&gt;
       +&lt;h2&gt;Conclusion&lt;/h2&gt;
       +&lt;p&gt;I do think that LLMs, in particular ChatGPT since this is the one I have more
       +experience with, are improving. But still, if there is anything that I
       +concluded after putting all those experiences in a post is that you still need
       +someone that knows at least as much about whatever you're asking ChatGPT to
       +actually validate the answer.&lt;/p&gt;
       +&lt;p&gt;Not only because LLMs hallucinates a lot, but also because even in the &amp;quot;good&amp;quot;
       +example the LLM needed extra input to figure out about the possible drawback of
       +the solution. The only case where I felt that ChatGPT outsmarted me was the
       +&amp;quot;amazing&amp;quot; case, but this was literally the only example that I could find in
       +all my chat history with ChatGPT that was like this.&lt;/p&gt;
       +&lt;p&gt;So yes, at least for now I still don't think AI will be our overlords. Maybe
       +someday.&lt;/p&gt;</summary></entry><entry><title>Making a blog for the next 10 years</title><link href="https://kokada.dev/blog/making-a-blog-for-the-next-10-years/" rel="alternate"/><published>2024-08-24T00:00:00+00:00</published><id>https://kokada.dev/blog/making-a-blog-for-the-next-10-years/</id><summary type="html">&lt;p&gt;So one thing that I realise after starting writing this blog is that I care
       +more about it than some of my other projects. For some reason or another I want
       +to make sure that this blog will continue with me for a long time. This is one
       +of the reasons why I use &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;GitHub as mirror
       +blog&lt;/a&gt; and why I
       +created a &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;bunch of
       +automation&lt;/a&gt;
       +to make sure I never forget how to maintain this blog.&lt;/p&gt;
       +&lt;p&gt;Still, there are a bunch of dependencies that I need to make sure they're
       +working so I can publish blog posts:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;Markdown&lt;/li&gt;
       +&lt;li&gt;A text editor&lt;/li&gt;
       +&lt;li&gt;Hosting and blog platform&lt;/li&gt;
       +&lt;li&gt;Shell script and
       +&lt;a href="https://kokada.dev/blog/abusing-makefiles-for-fun-and-profit"&gt;Makefile&lt;/a&gt;&lt;/li&gt;
       +&lt;li&gt;Go toolchain and modules&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;Let's start with the most important one: the texts themselves, they're all
       +written in &lt;a href="https://www.markdownguide.org/"&gt;Markdown&lt;/a&gt;. The nice thing about
       +Markdown is that it is just plain text files with some special notation that
       +allow you to format text, but the text itself is still legible even if you have
       +no support to render Markdowns. So it means as long as I can read a plain text
       +file, I can still read the posts, no issue there. Closely related issue here is
       +the text editor, but the nice thing about Markdown is that if tomorrow I decide
       +to change from &lt;code&gt;neovim&lt;/code&gt; to, say, &lt;a href="https://zed.dev/"&gt;Zed&lt;/a&gt;, I can still write it
       +Markdown files without issue. I also use a standardised Markdown implementation
       +called &lt;a href="https://commonmark.org/"&gt;CommonMark&lt;/a&gt;, that fixes a bunch of ambiguities
       +of the original Markdown spec.&lt;/p&gt;
       +&lt;p&gt;The next point is the host (&lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt;) and the blog
       +platform (&lt;a href="https://github.com/mataroa-blog/mataroa"&gt;Mataroa&lt;/a&gt;). One of the nice
       +things about Mataroa is that it avoids platform lock-in by having multiple ways
       +to &lt;a href="https://mataroa.blog/export/"&gt;export your posts&lt;/a&gt;. I could export everything
       +to &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;, for example, and serve the blog from &lt;a href="https://pages.github.com/"&gt;GitHub
       +Pages&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;This is especially nice consider how small &lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt; is,
       +so it is good to know that if I eventually have issues I could move to
       +somewhere else. I also have an e-mail backup every month with all posts made
       +(another &lt;a href="https://hey.mataroa.blog/blog/monthly-auto-exports-via-email/"&gt;Mataroa
       +feature&lt;/a&gt;), and
       +of course I have a &lt;a href="https://github.com/thiagokokada/blog"&gt;Git repo&lt;/a&gt; that also
       +acts as a &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;mirror of this
       +blog&lt;/a&gt;. So I would
       +say the chance of losing access to the content is slim.&lt;/p&gt;
       +&lt;p&gt;One other issue is the URL of the posts that are indexed in many different
       +places, like &lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;,
       +&lt;a href="https://lobste.rs/"&gt;Lobte.rs&lt;/a&gt;, etc. This is why I finally decide to bite the
       +bullet and purchase a proper domain, and this website should now be available
       +in &lt;a href="kokada.dev"&gt;kokada.dev&lt;/a&gt;. This means that in my eventual exit from
       +&lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt;, I can just point my new blog location to my own
       +domain if needed (it is not as easy since I also need to preserve the post
       +URLs, but shouldn't be difficult to fix this if I ever need to do so).&lt;/p&gt;
       +&lt;p&gt;Now for the tools that I use to publish from the original Markdown files to
       +everything else. Let's start with shell script(s) and Makefile: I decided that
       +they're less of an issue if they eventually stop working: they're only used to
       +make my life easier, but I can still publish files manually if needed. Still, I
       +tried to rewrite both the
       +&lt;a href="https://github.com/thiagokokada/blog/commit/a0d421ca90f3da059998295c5e3c6c7a6a3f0688"&gt;shell&lt;/a&gt;
       +and
       +&lt;a href="https://github.com/thiagokokada/blog/commit/074580065b21fbdaf930aa51968e69f015d49505"&gt;Makefile&lt;/a&gt;
       +to avoid GNUisms, so in the eventual case that I decide to e.g.: stop using a
       +GNU/Linux system like NixOS and use a *BSD system instead, I am covered.&lt;/p&gt;
       +&lt;p&gt;In other projects I would probably add a &lt;code&gt;shell.nix&lt;/code&gt; file and add the GNU
       +versions of the tools that I am using, but I am trying to avoid Nix while
       +building this blog as much as I can, mainly because it is not available
       +everywhere (e.g.: *BSD systems).&lt;/p&gt;
       +&lt;p&gt;Go is the more important part: the tooling used to &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;publish this blog is
       +written in
       +Go&lt;/a&gt;. Go
       +is a good language when you want to ensure that things will work for a long
       +time because of its &lt;a href="https://go.dev/blog/compat"&gt;backwards compatibility
       +guarantee&lt;/a&gt;. Also I don't expect Google dropping Go
       +development soon, but even if this happen (&lt;a href="https://killedbygoogle.com/"&gt;&amp;quot;killed by
       +Google&amp;quot;&lt;/a&gt; is a thing after all), it is very likely
       +some other group or company would adopt its development quickly, considering
       +&lt;a href="https://www.tiobe.com/tiobe-index/go/"&gt;how popular the language&lt;/a&gt; is.&lt;/p&gt;
       +&lt;p&gt;However, the &lt;a href="https://github.com/thiagokokada/blog/blob/main/go.mod"&gt;Go
       +modules&lt;/a&gt; that I depend
       +are another story:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;&lt;a href="https://github.com/elliotchance/orderedmap/"&gt;elliotchance/orderedmap&lt;/a&gt;: an
       +ordered map implementation that I use until Go adds it in the &lt;a href="https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go"&gt;standard
       +library&lt;/a&gt;&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/gorilla/feeds"&gt;gorilla/feeds&lt;/a&gt;: a RSS generator library&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/gosimple/slug"&gt;gosimple/slug&lt;/a&gt;: a
       +&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Slug"&gt;slug&lt;/a&gt; generator
       +library&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/yuin/goldmark"&gt;yuin/goldmark&lt;/a&gt;: a CommonMark parser and
       +renderer&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/teekennedy/goldmark-markdown"&gt;teekennedy/goldmark-markdown&lt;/a&gt;:
       +a renderer for Goldmark to render back to Markdown (since Goldmark itself
       +doesn't have this capacity)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;In common for all those modules are that they're clearly small projects
       +maintained mostly by one developer. They're all very good, don't get me wrong,
       +but they're still an reliability issue in the future. There is no guarantee
       +those repositories will not be deleted tomorrow, for example.&lt;/p&gt;
       +&lt;p&gt;Yes, &lt;a href="https://proxy.golang.org/"&gt;Go Proxy&lt;/a&gt; exist, but from what I understood
       +reading its page is that while it caches modules contents, this is not
       +guarantee:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;proxy.golang.org does not save all modules forever. There are a number of
       +reasons for this, but one reason is if proxy.golang.org is not able to detect
       +a suitable license. In this case, only a temporarily cached copy of the
       +module will be made available, and may become unavailable if it is removed
       +from the original source and becomes outdated. The checksums will still
       +remain in the checksum database regardless of whether or not they have become
       +unavailable in the mirror.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This is why this is the first project that made sense to me to use &lt;a href="https://go.dev/ref/mod#go-mod-vendor"&gt;&lt;code&gt;go mod vendor&lt;/code&gt;&lt;/a&gt;. Now I have a copy of the source
       +code of all modules inside the
       +&lt;a href="https://github.com/thiagokokada/blog/tree/0b97630d6b30551ffe05b5d8124305b1065f729d/vendor"&gt;vendor&lt;/a&gt;
       +directory in the repository, avoiding the risks I commented above. This allows
       +me to ensure that this blog will still be publishable in the future, as long as
       +I have a working Go toolchain (and Go toolchain makes this
       +&lt;a href="https://kokada.dev/blog/quick-bits-go-automatically-downloads-a-newer-toolchain-if-needed"&gt;easy&lt;/a&gt;).&lt;/p&gt;
       +&lt;p&gt;There are a few other things that can bitrot this blog, for example links going
       +nowhere. I always try to use
       +&lt;a href="https://en.wikipedia.org/wiki/Permalink"&gt;permalinks&lt;/a&gt; when it makes sense, but
       +the only actual way to ensure those links would work in the future would be to
       +point them to &lt;a href="https://archive.org/"&gt;archive.org&lt;/a&gt; (but even archive.org may not
       +exist forever). Maybe something to fix in the future, hope not in the far
       +future, before things start to break.&lt;/p&gt;</summary></entry><entry><title>An unordered list of hidden gems inside NixOS</title><link href="https://kokada.dev/blog/an-unordered-list-of-hidden-gems-inside-nixos/" rel="alternate"/><published>2024-08-21T00:00:00+00:00</published><id>https://kokada.dev/blog/an-unordered-list-of-hidden-gems-inside-nixos/</id><summary type="html">&lt;p&gt;After using &lt;a href="https://nixos.org/"&gt;NixOS&lt;/a&gt; for the last 5+ years as my main OS, I
       +end up with a &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;configuration&lt;/a&gt;
       +with many things that are interesting for one reason or another, but it is not
       +listed anywhere (well, except if you are the kind of person that reads &lt;code&gt;man 5 configuration.nix&lt;/code&gt; or the release notes in every release).&lt;/p&gt;
       +&lt;p&gt;So kind in the same spirit as my &lt;a href="https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go"&gt;list of things that I miss in
       +Go&lt;/a&gt;, here is
       +a list of modules that I find neat in NixOS and are not default already. Again,
       +the list is unordered since this makes it easier to update in the future if I
       +find something else, but also I don't want to think too hard about an order
       +here.&lt;/p&gt;
       +&lt;p&gt;With all above, let's start.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/6afb255d976f85f3359e4929abd6f5149c323a02/nixos/modules/services/networking/nftables.nix"&gt;&lt;code&gt;networking.nftables&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://www.nftables.org/"&gt;nftables&lt;/a&gt; is, accordingly to Wikipedia:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;nftables is a subsystem of the Linux kernel providing filtering and
       +classification of network packets/datagrams/frames.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;It is basically a replacement of the venerable
       +&lt;a href="https://en.wikipedia.org/wiki/Iptables"&gt;iptables&lt;/a&gt;, that still exist and is the
       +default program to configure the famous &lt;code&gt;networking.firewall&lt;/code&gt;, the declarative
       +&lt;a href="https://wiki.nixos.org/wiki/Firewall"&gt;Firewall&lt;/a&gt; that NixOS enable by default.&lt;/p&gt;
       +&lt;p&gt;To enable, it is simple, just add to your configuration:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;networking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nftables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And thanks to the fact that NixOS's Firewall is declarative, everything should
       +still work as expect: any service that you set &lt;code&gt;openFirewall = true&lt;/code&gt; will still
       +have its ports open, if you set &lt;code&gt;networking.firewall.allowPing = false&lt;/code&gt; it will
       +still disable pings like before, etc.&lt;/p&gt;
       +&lt;p&gt;If you look at the documentation of the above option, you will find the
       +following warning:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Note that if you have Docker enabled you will not be able to use nftables
       +without intervention. Docker uses iptables internally to setup NAT for
       +containers. This module disables the ip_tables kernel module, however Docker
       +automatically loads the module. Please see
       +&lt;a href="https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273"&gt;https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273&lt;/a&gt; for
       +more information.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;I don't use Docker (switched to Podman instead for quite a long time), so I
       +don't know how bad the situation is. Also keep in mind that &lt;code&gt;nftables&lt;/code&gt; does
       +offer &lt;code&gt;iptables-compat&lt;/code&gt; for compatibility with old iptables scripts, so it is
       +most likely Docker doing something weird here.&lt;/p&gt;
       +&lt;p&gt;Now, the actual advantage from the user here is not clear: the main advantage
       +from my point of view (and the reason I used to use in other distros like Arch)
       +is the improved syntax, however if you are using the declarative NixOS's
       +Firewall you are not interacting with either &lt;code&gt;iptables&lt;/code&gt; or &lt;code&gt;nftables&lt;/code&gt; directly
       +anyway. &lt;code&gt;nftables&lt;/code&gt; is supposed to be more efficient, but not sure most users
       +will care about this.&lt;/p&gt;
       +&lt;p&gt;However if you are the kind of person that needs custom rules, switching to
       +&lt;code&gt;nftables&lt;/code&gt; does bring a few benefits, including
       +&lt;code&gt;networking.nftables.checkRuleset&lt;/code&gt; (enabled by default), that checks if your
       +ruleset has syntax errors during build time. Really valuable to avoid issues
       +only after switch.&lt;/p&gt;
       +&lt;p&gt;Anyway, this is one of those options that I think it should be the default for
       +a long time, since most of the new development in NixOS firewall seems to be
       +focusing &lt;code&gt;nftables&lt;/code&gt; for a while.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/877d19523edcac81b167e8fd716ad2658da2adca/nixos/modules/system/activation/switchable-system.nix#L30-L38"&gt;&lt;code&gt;system.switch.enableNg&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://github.com/NixOS/nixpkgs/pull/308801"&gt;This one&lt;/a&gt; I just discovered
       +today, but it has been available for a while (~2 months if you're using
       +&lt;code&gt;nixos-unstable&lt;/code&gt;). Finally someone is rewriting
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/b1eff03c35aa7c90ab3a4d9f6ef297dae5fba37b/nixos/modules/system/activation/switch-to-configuration.pl"&gt;&lt;code&gt;switch-to-configuration.pl&lt;/code&gt;&lt;/a&gt;,
       +the Perl script that is called everytime you run &lt;code&gt;nixos-rebuild switch&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;Now, I am not one of those &amp;quot;rewrite in Rust&amp;quot; zealots, but in this case this is
       +definitely worth it: &lt;code&gt;switch-to-configuration.pl&lt;/code&gt; is one of those pieces of
       +code in NixOS that most people avoid touching at the fear of breaking
       +something. There is a reason why
       +&lt;a href="https://github.com/NixOS/nixpkgs/commit/eeb2588a59c938042b74183ce1da7052a6ef7e59"&gt;&lt;code&gt;nixos-rebuild&lt;/code&gt;&lt;/a&gt;
       +is as convoluted as it is, because even if it is a messy shell script, most
       +people preferred to workaround issues from the &lt;code&gt;switch-to-configuration.pl&lt;/code&gt;
       +inside it than trying to understand the mess that &lt;code&gt;switch-to-configuration.pl&lt;/code&gt;
       +is.&lt;/p&gt;
       +&lt;p&gt;Trying this one is easy:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enableNg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yes, you need to explicit set &lt;code&gt;system.switch.enable = false&lt;/code&gt;, since the default
       +is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;By the way, what is the reason you would want to set &lt;code&gt;system.switch.enable = false&lt;/code&gt; before the &lt;code&gt;enableNg&lt;/code&gt; appeared you ask? For systems that are immutable
       +and updated by e.g.: image upgrades instead of modifying root.&lt;/p&gt;
       +&lt;p&gt;Enabling &lt;code&gt;switch-to-configuration-ng&lt;/code&gt; right now is mostly for testing purposes,
       +but one of the advantages that I saw is that system switches are (slightly)
       +faster:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;sudo nixos-rebuild switch&amp;quot; # switch-to-configuration.pl
       +Benchmark 1: sudo nixos-rebuild switch
       +  Time (mean ± σ):      3.576 s ±  0.035 s    [User: 0.004 s, System: 0.014 s]
       +  Range (min … max):    3.522 s …  3.645 s    10 runs
       +&lt;/code&gt;&lt;p&gt;&lt;code&gt;$ hyperfine &amp;quot;sudo nixos-rebuild switch&amp;quot; # switch-to-configuration-ng
       +Benchmark 1: sudo nixos-rebuild switch
       +Time (mean ± σ):      3.394 s ±  0.080 s    [User: 0.004 s, System: 0.013 s]
       +Range (min … max):    3.325 s …  3.608 s    10 runs
       +&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
       +&lt;p&gt;But yes, the difference is not enough to make a significant impact, and it is
       +not the objective anyway. The real reason for the rewrite is to make it easier
       +to collaborate. I hope one day we also have someone brave enough to rewrite the
       +&lt;code&gt;nixos-rebuild&lt;/code&gt; script in something saner.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/cce9aef6fd8f010d288d685b9d2a38f3b6ac47e9/nixos/modules/system/boot/systemd/initrd.nix"&gt;&lt;code&gt;boot.initrd.systemd&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;A quick recap on how a modern Linux distro generally boots: the first thing
       +that the bootloader (say &lt;a href="https://www.gnu.org/software/grub/"&gt;GRUB&lt;/a&gt; or
       +&lt;a href="https://systemd.io/BOOT/"&gt;systemd-boot&lt;/a&gt;) loads is &lt;code&gt;initrd&lt;/code&gt; (&lt;em&gt;initial
       +ramdisk&lt;/em&gt;), a small image that runs from RAM and includes the Linux kernel and
       +some utilities that are responsible for setting up the main system. For
       +example, one of the responsibilities of the &lt;code&gt;initrd&lt;/code&gt; is to mount the disks and
       +start init system (&lt;code&gt;systemd&lt;/code&gt;).&lt;/p&gt;
       +&lt;p&gt;It may surprising that this &lt;code&gt;initrd&lt;/code&gt; image does &lt;strong&gt;not&lt;/strong&gt; generally include
       +&lt;code&gt;systemd&lt;/code&gt;. Traditionally &lt;code&gt;initrd&lt;/code&gt; is composed by a bunch of shell scripts and a
       +minimal runtime (e.g.: &lt;a href="https://www.busybox.net/"&gt;busybox&lt;/a&gt;), however &lt;code&gt;systemd&lt;/code&gt;
       +can also do this job since a long time ago. It is just the role of the distros
       +to integrate &lt;code&gt;systemd&lt;/code&gt; inside the &lt;code&gt;initrd&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;This is what &lt;code&gt;boot.initrd.systemd&lt;/code&gt; does: enable &lt;code&gt;systemd&lt;/code&gt; inside the &lt;code&gt;initrd&lt;/code&gt;.
       +It make a few subtle changes:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;If you are using &lt;a href="https://wiki.nixos.org/wiki/Full_Disk_Encryption"&gt;Full Disk Encryption via
       +LUKS&lt;/a&gt;, you will get a
       +different password prompt at login&lt;/li&gt;
       +&lt;li&gt;You will get &lt;code&gt;initrd&lt;/code&gt; time information if using &lt;code&gt;systemd-analyze&lt;/code&gt; to measure
       +boot time
       +&lt;ul&gt;
       +&lt;li&gt;You can get even more information (bootloader) if you also use
       +&lt;code&gt;systemd-boot&lt;/code&gt;&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;You will also get &lt;code&gt;systemd&lt;/code&gt; style status about services during &lt;code&gt;initrd&lt;/code&gt; (not
       +only afterwards)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;But I think the main reason is that since &lt;code&gt;systemd&lt;/code&gt; is event-driven, it should
       +make boot more reliable, especially in challenging situations (like booting
       +from network). I can't say that I have any system like this to test if it is
       +actually more reliable or not, but I don't remember having any issues since I
       +set &lt;code&gt;boot.initrd.systemd.enable = true&lt;/code&gt;, so there is that.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/b4a09f1f9d1599478afadffa782a02690550447c/pkgs/development/libraries/pipewire/default.nix"&gt;&lt;code&gt;services.pipewire&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;If there is something in that list that has a good chance that you're using
       +already, it is this one, especially if you're using
       +&lt;a href="https://wayland.freedesktop.org/"&gt;Wayland&lt;/a&gt;. Still, I think it is interesting
       +to include in this list since &lt;a href="https://www.pipewire.org/"&gt;PipeWire&lt;/a&gt; is great.&lt;/p&gt;
       +&lt;p&gt;The experience with PipeWire until now for me was seamless: I never had any
       +issues with it, all my applications still work exactly as it always worked. I
       +also didn't had any issues with
       +&lt;a href="https://www.freedesktop.org/wiki/Software/PulseAudio/"&gt;PulseAudio&lt;/a&gt; for a
       +while, but I still remember when I first tried PulseAudio during the 0.x in
       +Fedora and having tons of issues. So bonus points for PipeWire developers for
       +polishing the experience of enough that most people will feel no difference.&lt;/p&gt;
       +&lt;p&gt;To enable PipeWire, I would recommend:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pipewire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;alsa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pulse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# jack.enable = true;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This enables both ALSA and PulseAudio emulation support in PipeWire for maximum
       +compatibility with desktop applications (you can also enable
       +&lt;a href="https://jackaudio.org/"&gt;&lt;code&gt;jack&lt;/code&gt;&lt;/a&gt; if you use professional audio applications).
       +It also enables &lt;a href="https://github.com/heftig/rtkit"&gt;&lt;code&gt;rtkit&lt;/code&gt;&lt;/a&gt;, allowing PipeWire
       +to get (soft) realtime, helping avoiding cracks during high CPU load.&lt;/p&gt;
       +&lt;p&gt;I also recommend taking a look at the &lt;a href="https://wiki.nixos.org/wiki/PipeWire"&gt;Wiki
       +article&lt;/a&gt;, that has multiple interesting
       +configurations that can be added for low-latency setups or improved codecs for
       +Bluetooth devices.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/c9ec8289781a3c4ac4dd5c42c8d50dd65360e79c/nixos/modules/services/networking/networkmanager.nix#L264-L271"&gt;&lt;code&gt;networking.networkmanager.wifi.backend = &amp;quot;iwd&amp;quot;&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;There is a good change that you're using
       +&lt;a href="https://www.networkmanager.dev/"&gt;&lt;code&gt;NetworkManager&lt;/code&gt;&lt;/a&gt; to manage network,
       +especially for Wi-Fi. And if that is the case, I can't recommend enough
       +changing the backend from the default &lt;code&gt;wpa_supplicant&lt;/code&gt; to
       +&lt;a href="https://iwd.wiki.kernel.org/"&gt;&lt;code&gt;iwd&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;If you think that your Wi-Fi takes a long time to connect/re-connect, it may be
       +because &lt;code&gt;wpa_supplicant&lt;/code&gt;. &lt;code&gt;iwd&lt;/code&gt; seems much more optimised in this regard, and
       +since switching to it I never felt that my Wi-Fi was worse than other OSes (and
       +generally slightly better than Windows, but keep in mind that this is a
       +non-scientific comparison).&lt;/p&gt;
       +&lt;p&gt;Not saying that I never had Wi-Fi issues since switching to &lt;code&gt;iwd&lt;/code&gt;, however
       +switching back to &lt;code&gt;wpa_supplicant&lt;/code&gt; in those cases never fixed the issue (it was
       +the same or worse), so I assume either bad hardware or drivers in those cases.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/d5badef1e0416160298aad6dd8604eaf8b6e7e52/nixos/modules/system/boot/tmp.nix#L25-L32"&gt;&lt;code&gt;boot.tmp.useTmpfs&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Most Linux distro nowadays mount &lt;code&gt;/tmp&lt;/code&gt; inside
       +&lt;a href="https://en.wikipedia.org/wiki/Tmpfs"&gt;tmpfs&lt;/a&gt;, storing the files inside RAM (and
       +making them actually temporary, e.g.: does not persist between reboots). There
       +is a &lt;a href="https://lwn.net/Articles/499410/"&gt;long discussion&lt;/a&gt; whether this makes
       +sense or not, but it is a fact that using &lt;code&gt;/tmp&lt;/code&gt; as an storage for small files
       +generally makes sense.&lt;/p&gt;
       +&lt;p&gt;However, NixOS still stores &lt;code&gt;/tmp&lt;/code&gt; inside &lt;code&gt;/&lt;/code&gt; by default. The main reason for
       +this is because Nix, by default, still builds everything in &lt;code&gt;TMPDIR&lt;/code&gt; (that
       +defaults to &lt;code&gt;/tmp&lt;/code&gt; if not set). To fix the situation you can do:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useTmpfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nix-daemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TMPDIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/var/tmp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This ensures that Nix will build in &lt;code&gt;/var/tmp&lt;/code&gt;, that is the temporary directory
       +for large files.&lt;/p&gt;
       +&lt;p&gt;If you don't want to use &lt;code&gt;/tmp&lt;/code&gt; inside tmpfs for some reason, I recommend at
       +least setting &lt;code&gt;boot.tmp.cleanOnBoot = true&lt;/code&gt;. This is to avoid issues with e.g.:
       +mistakenly writing a program that depends in &lt;code&gt;/tmp&lt;/code&gt; being persistent, because
       +this is the behavior that most other distros expects.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/68142254d20256e617f596a5d50f9950f98fa36a/nixos/modules/config/zram.nix"&gt;&lt;code&gt;zramSwap&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Have a system with small amounts of RAM? &lt;del&gt;You can download more RAM for free,
       +just click this &lt;a href="https://downloadmoreram.com/"&gt;link&lt;/a&gt;.&lt;/del&gt; Well, just kidding, but
       +you can actually improve RAM usage by compressing it. This is what
       +&lt;a href="https://en.wikipedia.org/wiki/Zram"&gt;&lt;code&gt;zram&lt;/code&gt;&lt;/a&gt; does: it will create a block
       +device using part of your RAM that will be compressed.&lt;/p&gt;
       +&lt;p&gt;While this means that whatever is in the compressed part can't be used as-is
       +(it needs to be decompressed first), it means you can store a lot more
       +information. And since RAM is much faster than disk, this generally is a good
       +trade-off. It is still recommended that you have some swap space though, e.g.:
       +to swap pages that are unused.&lt;/p&gt;
       +&lt;p&gt;To enable it, you can do:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;zramSwap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;algorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zstd&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can play a little with the &lt;code&gt;zramSwap.algorithm&lt;/code&gt; parameter, but I recommend
       +either &lt;code&gt;lz4&lt;/code&gt; or &lt;code&gt;zstd&lt;/code&gt;. It is important for the compression algorithm to be
       +fast, since you probably want to minimise CPU usage. Well, unless you have a
       +very good CPU and small amounts of RAM, them it may make sense to use an
       +algorithm that compress better but uses more CPU.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/5356420466c4d7901b63acc5e337c5bf30573f8a/nixos/modules/services/misc/fstrim.nix"&gt;&lt;code&gt;services.fstrim&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Do you have a SSD? If so, you probably want to
       +&lt;a href="https://en.wikipedia.org/wiki/Trim_(computing)"&gt;trim&lt;/a&gt; it regularly to keep a
       +good performance. This should be as easy as:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fstrim&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One caveat though: if you are using Full Disk Encryption with LUKS, you also
       +need to enable &lt;code&gt;boot.initrd.luks.devices.&amp;lt;name&amp;gt;.allowDiscards&lt;/code&gt;, otherwise
       +&lt;code&gt;fstrim&lt;/code&gt; will not work. The reason that this is not enabled by default is
       +because there are some &lt;a href="https://asalor.blogspot.com/2011/08/trim-dm-crypt-problems.html"&gt;security
       +implications&lt;/a&gt;.
       +It shouldn't be an issue for most people, but if you are paranoid please assess
       +your risks first.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/e83fc891118e5e5660ccca000040e33b0a0a3080/nixos/modules/system/boot/binfmt.nix"&gt;&lt;code&gt;boot.binfmt.emulatedSystems&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;This is one of my favorites. Do you want to have the power to run binaries from
       +other architectures like
       +&lt;a href="https://en.wikipedia.org/wiki/ARM_architecture_family"&gt;ARM&lt;/a&gt; or
       +&lt;a href="https://en.wikipedia.org/wiki/RISC-V"&gt;RISC-V&lt;/a&gt;? In NixOS it is easy:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binfmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emulatedSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aarch64-linux&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;riscv64-linux&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will install a QEMU emulator for the target architectures, and configure
       +&lt;a href="https://en.wikipedia.org/wiki/Binfmt_misc"&gt;&lt;code&gt;binfmt&lt;/code&gt;&lt;/a&gt; so that the kernel will
       +automatically detect when a binary from another architecture is run,
       +running it with the QEMU emulator instead.&lt;/p&gt;
       +&lt;p&gt;Now, keep in mind that you still need e.g.: libraries for the target
       +architecture. However this is where the Nix magic comes in, you can easily
       +compile something to another architecture as:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; uname -a
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Linux sankyuu-nixos 6.10.3 #1-NixOS SMP PREEMPT_DYNAMIC Sat Aug  3 07:01:09 UTC 2024 x86_64 GNU/Linux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; nixpkgs
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#hello --system aarch64-linux
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; file ./result/bin/hello
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./result/bin/hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /nix/store/kz7xglxzhad64v667wwpn8vrxhjwcbna-glibc-2.39-52/lib/ld-linux-aarch64.so.1, for GNU/Linux 3.10.0, not stripped
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./result/bin/hello
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, world!
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And since the binary will be linked with the libraries from the target
       +architecture, everything works as expect.&lt;/p&gt;
       +&lt;p&gt;You also need to temper your expectations: QEMU emulation is slow. It is
       +sufficient fast to build small programs, but if you need to build something
       +bigger, I would recommend you to do it via &lt;a href="https://kokada.dev/blog/building-static-binaries-in-nix"&gt;cross-compilation
       +instead&lt;/a&gt;.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/d9cf6ea6f809b862b73766705bdadb1236eeeff8/nixos/modules/services/system/dbus.nix#L40-L50"&gt;&lt;code&gt;services.dbus.implementation = &amp;quot;broker&amp;quot;&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Someone in
       +&lt;a href="https://lobste.rs/s/ymmale/unordered_list_hidden_gems_inside_nixos#c_dz1hck"&gt;Lobste.rs&lt;/a&gt;
       +remembered me of this gem: &lt;a href="https://github.com/bus1/dbus-broker"&gt;dbus-broker&lt;/a&gt;,
       +a high performance implementation of
       +&lt;a href="https://en.wikipedia.org/wiki/D-Bus"&gt;D-Bus&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;For those who doesn't know, D-Bus is the &lt;a href="https://en.wikipedia.org/wiki/Inter-process_communication"&gt;Inter-Process
       +Communication&lt;/a&gt;
       +protocol used mostly in Linux desktop. And distros like
       +&lt;a href="https://archlinux.org/news/making-dbus-broker-our-default-d-bus-daemon/"&gt;Arch&lt;/a&gt;
       +and
       +&lt;a href="https://fedoraproject.org/wiki/Changes/DbusBrokerAsTheDefaultDbusImplementation"&gt;Fedora&lt;/a&gt;
       +already use it to default, so there aren't lots of reason to not use it.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/6afb255d976f85f3359e4929abd6f5149c323a02/nixos/modules/services/hardware/irqbalance.nix"&gt;&lt;code&gt;services.irqbalance&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-23&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Here is one that I have no experience but got curious after this comment in
       +&lt;a href="https://lobste.rs/s/ymmale/unordered_list_hidden_gems_inside_nixos#c_aghpwy"&gt;Lobte.rs&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;One more option to add is maybe &lt;code&gt;services.irqbalance.enable&lt;/code&gt;. For my old
       +laptop it’s a life savior: no more freezes of the DE or video playback during
       +heavy operations like e.g. compilation.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Never tried it before but will give a try!&lt;/p&gt;</summary></entry><entry><title>Quick bits: using forks with Go modules</title><link href="https://kokada.dev/blog/quick-bits-using-forks-with-go-modules/" rel="alternate"/><published>2024-08-20T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-using-forks-with-go-modules/</id><summary type="html">&lt;p&gt;There are 2 types of blog posts: the ones you write for others, and the ones
       +that you write for yourself. This blog post is the latter kind. What I am going
       +to talk here is probably something most people know, but I didn't, and the
       +documentation is all scattered in the internet. So I am writing mostly to
       +remember myself, in future, if I have the same kind of issue what I need to do.&lt;/p&gt;
       +&lt;p&gt;The context: &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt;, the blog platform
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; is hosted on, relies in
       +&lt;a href="https://python-markdown.github.io/"&gt;python-markdown&lt;/a&gt;, predates
       +&lt;a href="https://commonmark.org/"&gt;CommonMark&lt;/a&gt; specification and as such, has some
       +incompatibilities. One of those incompatibilities with CommonMark is the way
       +sub lists are handled. From the documentation:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;The syntax rules clearly state that when a list item consists of multiple
       +paragraphs, “each subsequent paragraph in a list item must be indented by
       +either 4 spaces or one tab” (emphasis added). However, many implementations
       +do not enforce this rule and allow less than 4 spaces of indentation. The
       +implementers of Python-Markdown consider it a bug to not enforce this rule.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;CommonMark &lt;a href="https://spec.commonmark.org/0.31.2/#lists"&gt;relax those
       +restrictions&lt;/a&gt;, allowing a sublist to
       +be defined with just 2 spaces of indentation.&lt;/p&gt;
       +&lt;p&gt;So I have automated all posts from this blog &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;using
       +Go&lt;/a&gt; and
       +a CommonMark renderer called &lt;a href="https://github.com/yuin/goldmark/"&gt;Goldmark&lt;/a&gt;. I
       +them re-render the Markdown to Mataroa using a &lt;a href="https://github.com/teekennedy/goldmark-markdown"&gt;Markdown
       +renderer&lt;/a&gt; before publising to
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt;, because this allow me to do some
       +transformations in the original Markdown. It mostly works fine except for sub
       +lists, thanks to the fact that the Markdown renderer I am using renders sub
       +lists with 2 spaces.&lt;/p&gt;
       +&lt;p&gt;The only reason sub lists are working right now is because
       +&lt;a href="https://gluer.org/"&gt;@ratsclub&lt;/a&gt; fixed this issue in the fork that
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; runs. But I want to be compatible with
       +the official instance if I ever need to migrate.&lt;/p&gt;
       +&lt;p&gt;The solution? Let's fix this in a
       +&lt;a href="https://github.com/teekennedy/goldmark-markdown/pull/21"&gt;PR&lt;/a&gt;. However now that
       +I have code to fix the issue, how can I use it without waiting upstream to
       +merge my code?&lt;/p&gt;
       +&lt;p&gt;If you are using Go modules it is easy, you just need to use the &lt;a href="https://go.dev/ref/mod#go-mod-file-replace"&gt;&lt;code&gt;replace&lt;/code&gt;
       +directive&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;thiagokokada&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="mf"&gt;1.23&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;elliotchance&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;orderedmap&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt; &lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="mf"&gt;.4.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gorilla&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;feeds&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.2.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gosimple&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.14.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;teekennedy&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.3.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;yuin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.7.4&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;yuin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;highlighting&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.0.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20220208100518&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;594&lt;/span&gt;&lt;span class="nx"&gt;be1970594&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;alecthomas&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;chroma&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.10.0&lt;/span&gt; &lt;span class="c1"&gt;// indirect
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dlclark&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;regexp2&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.11.4&lt;/span&gt; &lt;span class="c1"&gt;// indirect
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gosimple&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;unidecode&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.0.1&lt;/span&gt; &lt;span class="c1"&gt;// indirect
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;replace&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;teekennedy&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;thiagokokada&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.0.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20240820111219&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f30775d8ed15&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will replace all usages of &lt;code&gt;github.com/teekennedy/goldmark-markdown&lt;/code&gt; to my
       +fork in &lt;code&gt;github.com/thiagokokada/goldmark-markdown&lt;/code&gt;. You even get all the
       +reproducibility of modules since Go automatically pins the commit.&lt;/p&gt;
       +&lt;p&gt;Since the Go format for versions is quite unique, you can just set to the
       +desired branch (e.g.: instead of &lt;code&gt;v0.0.0-20240820111219-f30775d8ed15&lt;/code&gt;, you can
       +use &lt;code&gt;add-sublist-length-opt&lt;/code&gt; that is the branch name) and run &lt;code&gt;go mod tidy&lt;/code&gt; to
       +fix the format.&lt;/p&gt;</summary></entry><entry><title>Quick bits: Go automatically downloads a newer toolchain if needed</title><link href="https://kokada.dev/blog/quick-bits-go-automatically-downloads-a-newer-toolchain-if-needed/" rel="alternate"/><published>2024-08-19T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-go-automatically-downloads-a-newer-toolchain-if-needed/</id><summary type="html">&lt;p&gt;I am using
       +&lt;a href="https://github.com/elliotchance/orderedmap/"&gt;elliotchance/orderedmap&lt;/a&gt; as my
       +choice of ordered maps (since Go &lt;a href="https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go"&gt;doesn't have
       +one&lt;/a&gt; in
       +standard library yet). I recently did a
       +&lt;a href="https://github.com/elliotchance/orderedmap/pull/41"&gt;PR&lt;/a&gt; to implement Go 1.23
       +iterators, because I find them neat, however I was avoiding to use it in the
       +code that generates this &lt;a href="https://github.com/thiagokokada/blog"&gt;blog&lt;/a&gt; since Go
       +1.23 was just released and is still not the default Go in
       +&lt;a href="https://github.com/NixOS/nixpkgs"&gt;nixpkgs&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;I decided that I would create a
       +&lt;a href="https://github.com/thiagokokada/blog/pull/2"&gt;branch&lt;/a&gt; and leave there for a few
       +months, until I decided to try to run the code locally and got this:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go build
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go: downloading go1.23.0 (darwin/arm64)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice. And before you ask, yes, the compiled binary works perfectly:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog &amp;gt; README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog -rss &amp;gt; rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So how does this work? Take a look at the documentation in the official &lt;a href="https://tip.golang.org/doc/toolchain"&gt;Golang
       +page&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Starting in Go 1.21, the Go distribution consists of a go command and a
       +bundled Go toolchain, which is the standard library as well as the compiler,
       +assembler, and other tools. The go command can use its bundled Go toolchain
       +as well as other versions that it finds in the local PATH or downloads as
       +needed.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;There are a bunch of rules here that I am not going to enter in detail (I
       +recommend you to read the official documentation), but a quick summary:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;
       +&lt;p&gt;Go will download a toolchain when either &lt;code&gt;go&lt;/code&gt; or &lt;code&gt;toolchain&lt;/code&gt; lines &lt;code&gt;go.mod&lt;/code&gt;
       +is set to a Go version higher than your current &lt;code&gt;go&lt;/code&gt; binary&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;But only if your &lt;code&gt;go&lt;/code&gt; binary is at least version 1.21, since this is the
       +version that introduces this behavior&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;
       +&lt;p&gt;You can force a specific toolchain with &lt;code&gt;GOTOOLCHAIN&lt;/code&gt; environment setting,
       +e.g.: &lt;code&gt;GOTOOLCHAIN=1.23&lt;/code&gt;&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;The default value for &lt;code&gt;GOTOOLCHAIN&lt;/code&gt; is &lt;code&gt;auto&lt;/code&gt;, that basically has the
       +behavior described in this post&lt;/li&gt;
       +&lt;li&gt;You can also set to &lt;code&gt;local&lt;/code&gt; to always use the current &lt;code&gt;go&lt;/code&gt; binary, or the
       +previous behaviour pre-1.21 Go&lt;/li&gt;
       +&lt;li&gt;There is also &lt;code&gt;&amp;lt;name&amp;gt;+auto&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt; options, that can be seen in the
       +docs&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;
       +&lt;p&gt;The downloaded toolchains go to whatever your &lt;code&gt;GOPATH&lt;/code&gt; is, inside
       +&lt;code&gt;golang.org/toolchain&lt;/code&gt; module, and version &lt;code&gt;v0.0.1-goVERSION.GOOS-GOARCH&lt;/code&gt;, for
       +example:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah &lt;span class="nv"&gt;$GOPATH&lt;/span&gt;/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.darwin-arm64
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;total 64
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x  14 user  staff   448B 19 Aug 12:01 .
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;drwxr-xr-x   4 user  staff   128B 19 Aug 12:01 ..
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.3K 19 Aug 12:01 CONTRIBUTING.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.4K 19 Aug 12:01 LICENSE
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.3K 19 Aug 12:01 PATENTS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.4K 19 Aug 12:01 README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   426B 19 Aug 12:01 SECURITY.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff    35B 19 Aug 12:01 VERSION
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x   4 user  staff   128B 19 Aug 12:01 bin
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff    52B 19 Aug 12:01 codereview.cfg
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   505B 19 Aug 12:01 go.env
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x   3 user  staff    96B 19 Aug 12:01 lib
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x   4 user  staff   128B 19 Aug 12:01 pkg
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x  77 user  staff   2.4K 19 Aug 12:02 src
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;By the way, this only works well because Go toolchain binaries are static.&lt;/p&gt;
       +&lt;p&gt;While I don't like a program downloading random binaries from the internet, I
       +like what Go is doing here. It makes the whole bootstrapping process for a Go
       +project much easier: as long as you have a reasonable up-to-date &lt;code&gt;go&lt;/code&gt; binary in
       +your &lt;code&gt;PATH&lt;/code&gt;, you should be ready to go (pun intended). And Go modules are
       +already reasonable secure, ensuring that each module have a proper checksum. As
       +long as nobody else can publish modules in &lt;code&gt;golang.org/toolchain&lt;/code&gt; namespace I
       +can't see much of a security issue here, but I am not a security expert.&lt;/p&gt;
       +&lt;p&gt;But if you don't like this behavior, you can always disable it by setting
       +&lt;code&gt;GOTOOLCHAIN=local&lt;/code&gt;. And just do not forget to set this in your
       +&lt;a href="https://brandur.org/fragments/go-version-matrix"&gt;CI&lt;/a&gt;, unless you don't care
       +about Go versions.&lt;/p&gt;</summary></entry><entry><title>An unordered list of things I miss in Go</title><link href="https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go/" rel="alternate"/><published>2024-08-17T00:00:00+00:00</published><id>https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go/</id><summary type="html">&lt;p&gt;I like Go. I think it is a &lt;a href="https://kokada.dev/blog/go-a-reasonable-good-language"&gt;reasonable good
       +language&lt;/a&gt;, and has some
       +good qualities that makes up for its flaws. However, this doesn't mean I think
       +the language couldn't be better, far from it.&lt;/p&gt;
       +&lt;p&gt;This blog post is a list of things that I miss from Go from other languages.
       +Some of the things here could probably be implemented soon, some other would
       +probably need a major revision of the language. The list is unordered, because
       +this makes it easier for me to update in the future if I found something else,
       +but also because I don't want to think too hard about giving each point here a
       +rank.&lt;/p&gt;
       +&lt;p&gt;With all above, let's start.&lt;/p&gt;
       +&lt;h2&gt;Ordered maps in standard library&lt;/h2&gt;
       +&lt;p&gt;When I first learned about
       +&lt;a href="https://docs.python.org/3/library/stdtypes.html#typesmapping"&gt;dictionaries&lt;/a&gt; in
       +Python it quickly became one of my favorite data structures ever. They're
       +extremely versatile, and most modern programming languages have something
       +similar in its standard library. Go isn't different, it has
       +&lt;a href="https://go.dev/blog/maps"&gt;&lt;code&gt;map&lt;/code&gt;&lt;/a&gt;, that is Go implementation of a &lt;a href="https://en.wikipedia.org/wiki/Hash_table"&gt;hash
       +table&lt;/a&gt;. However &lt;code&gt;map&lt;/code&gt; in Go are
       +quirky, for example:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;foo&amp;#34;&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="s"&gt;&amp;#34;bar&amp;#34;&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="s"&gt;&amp;#34;baz&amp;#34;&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="s"&gt;&amp;#34;qux&amp;#34;&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="s"&gt;&amp;#34;quux&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go run ./test.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;qux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;quux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go run ./test.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;qux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;quux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go run ./test.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;qux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;quux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, I don't expect any hash table implementation to keep the order of the
       +elements, but Go actually &lt;a href="https://victoriametrics.com/blog/go-map/"&gt;randomise each map
       +instance&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;But here’s the deal, while the hash function used for maps in Go is
       +consistent across all maps with &lt;strong&gt;the same key type&lt;/strong&gt;, the &lt;code&gt;seed&lt;/code&gt; used by
       +that hash function is different for each map instance. So, when you create a
       +new map, Go generates a random seed just for that map.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;While I understand the reason for this (i.e.: to avoid developers relying in a
       +specific iteration order), I still find it weird, and I think this is something
       +unique for Go. This decision means that even if you don't care about a specific
       +order, you will still need to sort the map before doing something else if you
       +want reproducibility, something that I care a lot.&lt;/p&gt;
       +&lt;p&gt;The fix for this? Go could offer an ordered map implementation inside the
       +standard library. An ordered map ensure that the iteration order of the map is
       +the same as the insertion order (that is, by the way, a powerful property that
       +allow maps to be used in other contexts, not just my pet peeve above).&lt;/p&gt;
       +&lt;p&gt;Python actually does this for any dictionaries since &lt;a href="https://stackoverflow.com/a/39980744"&gt;Python
       +3.6&lt;/a&gt;, but it offered an
       +&lt;a href="https://docs.python.org/3/library/collections.html#collections.OrderedDict"&gt;OrderedDict&lt;/a&gt;
       +before it (and &lt;code&gt;OrderedDict&lt;/code&gt; still has some methods that normal &lt;code&gt;dict&lt;/code&gt; doesn't,
       +that maybe useful in specific cases).&lt;/p&gt;
       +&lt;p&gt;Before generics it would be impossible to have a type-safe API for such data
       +structure without introducing a new data type in the language (like &lt;code&gt;slices&lt;/code&gt;),
       +but now Go has generics so it is not an issue anymore. The other issue is that
       +you would be forced to iterate manually in this new data structure, but thanks
       +to the new &lt;a href="https://tip.golang.org/doc/go1.23#language"&gt;&lt;code&gt;range-over-func&lt;/code&gt;&lt;/a&gt; in
       +Go 1.23, it means we can iterate in an ordered map as a library almost exactly
       +like we can do as a &lt;code&gt;map&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;orderedmap&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;orderedmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;foo&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bar&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;baz&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Iterator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Order always will be: foo, bar, baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, of course the lack of Ordered Map in the standard library can be filled
       +with third party implementations, e.g.: I am using this
       +&lt;a href="https://github.com/elliotchance/orderedmap"&gt;one&lt;/a&gt; in one of my projects. But
       +being in standard library reduces the friction: if there was some
       +implementation in standard library, I would generally prefer it unless I have
       +some specific needs. However when the standard library doesn't offer what I
       +need, I need to find it myself a suitable library, and this ends up taking time
       +since generally there are lots of alternatives.&lt;/p&gt;
       +&lt;h2&gt;Keyword and default arguments for functions&lt;/h2&gt;
       +&lt;p&gt;Something that comes straight from Python that I miss sometimes in Go is that
       +you can do things like this when declaring a function:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;World&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;print&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;#34;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#34;normal&amp;#34; function call&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# calling with keyword arguments&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# calling with default arguments&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python hello.py
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, Foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, Bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, World
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The lack of default arguments especially affects even some of the API decisions
       +for Go standard library, for example, &lt;code&gt;string.Replace&lt;/code&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;&lt;code&gt;func Replace(s, old, new string, n int) string&lt;/code&gt;&lt;/p&gt;
       +&lt;p&gt;Replace returns a copy of the string s with the first n non-overlapping
       +instances of old replaced by new. If old is empty, it matches at the
       +beginning of the string and after each UTF-8 sequence, yielding up to k+1
       +replacements for a k-rune string. If n &amp;lt; 0, there is no limit on the number
       +of replacements.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;If Go had default arguments, &lt;code&gt;Replace&lt;/code&gt; could have e.g.: &lt;code&gt;func Replace(s, old, new string, n int = -1)&lt;/code&gt; signature, that would mean we don't need
       +&lt;code&gt;strings.ReplaceAll&lt;/code&gt; (since this is basically what this function does, call
       +&lt;code&gt;strings.Replace(s, old, new, -1)&lt;/code&gt;).&lt;/p&gt;
       +&lt;h2&gt;Nullability (or nillability)&lt;/h2&gt;
       +&lt;p&gt;I talked I little about this in &lt;a href="https://kokada.dev/blog/go-a-reasonable-good-language"&gt;my previous post about
       +Go&lt;/a&gt;, but I want to
       +expand here.&lt;/p&gt;
       +&lt;p&gt;First, I don't think the language needs to support the generic solution for
       +nullability, that would be either having proper Union or Sum types. Kotlin
       +AFAIK doesn't support neither, but my 2 years experience with Kotlin showed
       +that just having nullable types already helped a lot in ensuring type safety.&lt;/p&gt;
       +&lt;p&gt;Second, I do feel that Go has less issues with &lt;code&gt;nil&lt;/code&gt; values, than say, Java,
       +because its decision of using zero values instead of &lt;code&gt;nil&lt;/code&gt; in many cases. So
       +for example, a string can never be &lt;code&gt;nil&lt;/code&gt;, however a string pointer can be. This
       +means that this is fine:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// do something with s
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// s maybe nil here, better check first
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Still, I get more &lt;code&gt;panic&lt;/code&gt; for &lt;code&gt;nil&lt;/code&gt; pointer deference than I get in other
       +languages that offer nullables (heck, even Python with
       +&lt;a href="https://www.mypy-lang.org/"&gt;&lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt; is safer).&lt;/p&gt;
       +&lt;p&gt;Sadly this is the change in this post that is more likely to need a completely
       +new revision of the language.
       +&lt;a href="https://github.com/golang/go/issues/49202"&gt;nillability&lt;/a&gt; was proposed before,
       +but it is really unlikely it can be done without breaking backwards
       +compatibility.&lt;/p&gt;
       +&lt;p&gt;It could be done the Java way by adding a &lt;code&gt;nullable&lt;/code&gt; type to the standard
       +library (&lt;a href="https://jcp.org/en/jsr/detail?id=305"&gt;JSR305&lt;/a&gt;), but the fact that
       +&lt;a href="https://stackoverflow.com/questions/2289694/what-is-the-status-of-jsr-305"&gt;JSR305 is considerd
       +dead&lt;/a&gt;
       +by many shows how difficult it is to do something like this without a major
       +change in the language. Dart is the only language that I know that &lt;a href="https://dart.dev/null-safety/understanding-null-safety"&gt;did this
       +successfully&lt;/a&gt;, but
       +definitely it was not without its pains. And the fact that most people that
       +program in Dart probably does because of Flutter (that eventually required
       +newer versions with null-safety) is not a good sign.&lt;/p&gt;
       +&lt;h2&gt;Lambdas&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-18&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Go is a surprising good language for some functional code, thanks to having
       +first class functions and closures. Sadly the syntax doesn't help, since the
       +only way you can use anonymous functions in Go is using &lt;code&gt;func&lt;/code&gt;. Especially if
       +the types are complex, this can result in some convoluted code. Take the
       +example from the &lt;a href="https://go.dev/wiki/RangefuncExperiment"&gt;&lt;code&gt;range-over-func&lt;/code&gt;
       +experiment&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;slices&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yield&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If Go had a syntax for lambdas, especially if we could elide the types, this
       +could be simplified a lot:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;slices&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or even something like this would already help, no special syntax but allowing
       +the types to be elided in an unnamed function:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;slices&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This feature I am still somewhat hopeful that may become a reality in some
       +future version of the language, since they didn't close the
       +&lt;a href="https://github.com/golang/go/issues/21498"&gt;issue&lt;/a&gt; yet, and the discussion
       +about the possibility of this feature is still ongoing.&lt;/p&gt;
       +&lt;h2&gt;Error on unused return values&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-09-12&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;In a &lt;a href="https://kokada.dev/blog/my-coding-experience-with-llm"&gt;recent post about
       +LLMs&lt;/a&gt;, I talked about
       +this function that I asked ChatGPT to generate to create a socket that accepted
       +a &lt;a href="https://pkg.go.dev/context"&gt;context&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// Start a goroutine to perform the read
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;// Context was canceled or timed out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="c1"&gt;// Wait for the read to finish
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read finished successfully
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However while reading &lt;a href="https://joeduffyblog.com/2016/02/07/the-error-model/"&gt;this blog
       +post&lt;/a&gt; about &amp;quot;The Error
       +Model&amp;quot;, I realised that this function is lacking error check during
       +&lt;code&gt;conn.SetReadDeadline()&lt;/code&gt; calls:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Start a goroutine to perform the read
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Reset read deadline
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                                &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Make sure that the goroutine is done to avoid leaks
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I could blame the LLM on this, but this is the type of mistake that I could see
       +happening even in a Pull Request done by an actual human.&lt;/p&gt;
       +&lt;p&gt;Ignoring errors is bad, especially because once the error is ignored it is
       +gone, forever. You may have strange issues in the code that are impossible to
       +debug because you can't know about something that doesn't exist. This is one of
       +the reasons I think exceptions are better, since this is the kind of error that
       +would be impossible to ignore if the language had exceptions, and the exception
       +would eventually propagate in your stack until it is captured or you get a
       +crash (with a stack trace to debug the issue).&lt;/p&gt;
       +&lt;p&gt;Now, I don't think this is a fault of using error as values instead of
       +exceptions. Go has the tools to encapsulate the errors and propagate them
       +properly, like it is shown in the fixed code. But the fact that someone can do
       +this mistake without any warning or error from the compiler is bad.&lt;/p&gt;
       +&lt;p&gt;If Go had a warning or error for unused return values, this would be different:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;  &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now in this case it is clear: I am ignoring the error explicitly, probably for
       +a good reason. I can ask during a Pull Request why the committer is ignoring if
       +it is lacking appropriate context, and maybe even ask for some comments to be
       +added why this would be safe. What can't happen is this being ignored by
       +mistake.&lt;/p&gt;
       +&lt;p&gt;I am not completely sure if I want this only for errors or for any unused value
       +though. There are lots of cases where ignoring non-error values is ok, but I
       +also don't see the issue of having something like:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;  &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;FuncThatReturnIntAndError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If anything, it is clear for whoever is reading this code later that you're
       +only calling this function only for its side-effects.&lt;/p&gt;
       +&lt;p&gt;By the way, yes, &lt;a href="https://github.com/kisielk/errcheck"&gt;errcheck&lt;/a&gt; linter exists,
       +but if the language itself doesn't enforce this it means that there are lots of
       +libraries that are probably mistakenly ignoring errors. And if the library is
       +ignoring errors by mistake, you can't do much when you actually have them.&lt;/p&gt;</summary></entry><entry><title>Quick bits: NixOS's boot.kernelPackages is a attrset</title><link href="https://kokada.dev/blog/quick-bits-nixoss-boot-kernelpackages-is-a-attrset/" rel="alternate"/><published>2024-08-15T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-nixoss-boot-kernelpackages-is-a-attrset/</id><summary type="html">&lt;p&gt;I don't know if this is just something that got me by surprise or not, but
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/bb16119a4a7639ebbc91ad0f516b324b0f7c9b68/nixos/modules/system/boot/kernel.nix#L40-L71"&gt;&lt;code&gt;boot.kernelPackages&lt;/code&gt;&lt;/a&gt;
       +option does not receive a derivation like most other packages, but instead
       +receives a function and returns an attribute set with all packages. Here is the
       +documentation:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;This option allows you to override the Linux kernel used by NixOS. Since
       +things like external kernel module packages are tied to the kernel you’re
       +using, it also overrides those. This option is a function that takes Nixpkgs
       +as an argument (as a convenience), and returns an attribute set containing at
       +the very least an attribute kernel. Additional attributes may be needed
       +depending on your configuration. For instance, if you use the NVIDIA X
       +driver, then it also needs to contain an attribute &lt;code&gt;nvidia_x11&lt;/code&gt;.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;The kernel package itself is referenced by the
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/bb16119a4a7639ebbc91ad0f516b324b0f7c9b68/nixos/modules/system/boot/kernel.nix#L331-L332"&gt;&lt;code&gt;kernel&lt;/code&gt;&lt;/a&gt;
       +derivation inside this attribute set:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;nix-repl&amp;gt; nixosConfigurations.sankyuu-nixos.config.boot.kernelPackages.kernel
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;«derivation /nix/store/5zyjvf3qgfk52qmgxh36l4dkr9lf100x-linux-6.10.3.drv»
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The reason for this is because it ensure that things like modules are built
       +with the same kernel version you are booting.&lt;/p&gt;
       +&lt;p&gt;However one less obvious consequence about this is that if you want packages
       +that come from &lt;code&gt;linuxPackages&lt;/code&gt;, say for example
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/os-specific/linux/cpupower/default.nix"&gt;&lt;code&gt;cpupower&lt;/code&gt;&lt;/a&gt;
       +or
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/os-specific/linux/cpupower/default.nix"&gt;&lt;code&gt;turbostat&lt;/code&gt;&lt;/a&gt;,
       +it is better to do:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernelPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpupower&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernelPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turbostat&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;linuxPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpupower&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;linuxPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turbostat&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, I used the later for a long time and never had issues. But technically
       +those packages depends in a specific kernel version for a reason, so getting
       +them from &lt;code&gt;config.boot.kernelPackages&lt;/code&gt; reduces the chance of you having some
       +compatibility issue in the future.&lt;/p&gt;</summary></entry><entry><title>Abusing Makefiles for fun and profit</title><link href="https://kokada.dev/blog/abusing-makefiles-for-fun-and-profit/" rel="alternate"/><published>2024-08-13T00:00:00+00:00</published><id>https://kokada.dev/blog/abusing-makefiles-for-fun-and-profit/</id><summary type="html">&lt;p&gt;If you are following this blog for a while, it should be no surprise that most
       +of the workflow in this blog is &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;automated using
       +Go&lt;/a&gt;. I
       +basically write Markdown files with some special rules inside the
       +&lt;a href="https://github.com/thiagokokada/blog"&gt;repository&lt;/a&gt;, commit and push it. In
       +seconds, the CI (currently &lt;a href="https://github.com/thiagokokada/blog/blob/4e3f25485c6682f3e066b219df2290934bc0d256/.github/workflows/go.yml"&gt;GitHub
       +Actions&lt;/a&gt;)
       +will take the latest commit, generate some files (since I use the &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;repository
       +itself&lt;/a&gt; as a
       +backup blog) and publish to the &lt;a href="https://kokada.capivaras.dev/"&gt;capivaras.dev
       +website&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;Now, considering how much about &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt; I talk in this blog,
       +it should be a surprise that the workflow above has &lt;strong&gt;zero&lt;/strong&gt; Nix code inside
       +it. I am not saying this blog will never have it, but I am only going to add if
       +this is necessary, for example if I start using a tool to build this blog that
       +I generally don't expect it to be installed by the machine I am currently
       +using. Go is an exception of this rule since it is relatively straightfoward to
       +install (just download the &lt;a href="https://go.dev/doc/install"&gt;binary&lt;/a&gt;) and because
       +its &lt;a href="https://go.dev/doc/go1compat"&gt;stability guarantee&lt;/a&gt; means (hopefully) no
       +breakage. But most other things I consider moving targets, and I wouldn't be
       +comfortable to use unless I have Nix to ensure reproducibility.&lt;/p&gt;
       +&lt;p&gt;This is why the other tool that this blog (ab)uses during its workflow is
       +&lt;a href="https://en.wikipedia.org/wiki/Make_(software)"&gt;&lt;code&gt;Make&lt;/code&gt;&lt;/a&gt;, one of the oldest
       +build automation tool that exist. It is basically available in any *nix (do not
       +confuse with &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt;) system, from most Linux distros to
       +macOS, by default. So it is the tool I choose to automatise some tasks in this
       +blog, even if I consider writing a &lt;code&gt;Makefile&lt;/code&gt; (the domain-specific language
       +that &lt;code&gt;Make&lt;/code&gt; uses) kind of a lost, dark art.&lt;/p&gt;
       +&lt;p&gt;To be clear, the idea of this post is not to be a &lt;code&gt;Makefile&lt;/code&gt; tutorial. I will
       +explain some basic concepts, but if you want an actual tutorial a good one can
       +be found &lt;a href="https://makefiletutorial.com/"&gt;here&lt;/a&gt;. Also, while I am using &lt;code&gt;Make&lt;/code&gt;
       +thanks to the reasons above, you can use many other tools for a similar
       +objective, like &lt;a href="https://github.com/casey/just"&gt;Justfiles&lt;/a&gt;,
       +&lt;a href="https://taskfile.dev/"&gt;Taskfiles&lt;/a&gt; (sadly it uses
       +&lt;a href="https://kokada.dev/blog/generating-yaml-files-with-nix"&gt;YAML&lt;/a&gt;), or even a
       +small script written in any language you want. The reason that I am writing
       +this post is why you should do it, not how.&lt;/p&gt;
       +&lt;p&gt;A quick recap on how this blog works: inside the
       +&lt;a href="https://github.com/thiagokokada/blog"&gt;repository&lt;/a&gt;, a post is basically a
       +Markdown post following the directory structure below
       +(&lt;a href="https://github.com/thiagokokada/blog/tree/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7"&gt;permalink&lt;/a&gt;
       +for the current version of this blog):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;.
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;...&amp;gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-07
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   ├── 01-quick-bits-is-crostini-a-microvm.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── 02-meta-are-quick-bits-really-quick.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-11
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── 01-building-static-binaries-in-nix.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-12
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   ├── 01-things-i-dont-like-in-my-chromebook-duet-3.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── Screenshot_2024-08-12_20.50.42.png
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-13
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   ├── 01-abusing-makefiles-for-fun-and-profit.md &amp;lt;-- this file
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── .github
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── workflows
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│       └── go.yml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── .gitignore
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── go.mod
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── go.sum
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── LICENSE
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── link_rewriter.go
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── Makefile
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── mataroa.go
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;└── .scripts
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;    └── gen-post.sh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So I just create a new Markdown file following the
       +&lt;code&gt;YYYY-MM-DD/XX-title-slug.md&lt;/code&gt; format. It &lt;strong&gt;must&lt;/strong&gt; start with a &lt;code&gt;h1&lt;/code&gt; header,
       +that will be automatically extract to be used as the post title, but otherwise
       +there is no other formatting rules. It is a highly optionated structure, but
       +the nice thing about being optionated is that we can extract lots of
       +information just from how the files are organised in the filesystem.&lt;/p&gt;
       +&lt;p&gt;Most of the magic that converts those Markdown files to actual blog posts are
       +in the Go files that you can see above: &lt;code&gt;blog.go&lt;/code&gt; is the main logic that walks
       +in the repository and extracts the necessary information, &lt;code&gt;mataroa.go&lt;/code&gt; is
       +responsible for the &lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt; integration (that
       +uses &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt; platform), while &lt;code&gt;link_rewriter.go&lt;/code&gt; is
       +responsible to do some transformations in the Markdown files before posting.&lt;/p&gt;
       +&lt;p&gt;While I could manage everything by just using &lt;code&gt;go&lt;/code&gt; CLI and a few other *nix
       +commands, to make it easier to manager everything I have the following
       +&lt;a href="https://github.com/thiagokokada/blog/blob/527466a2a7c8baae532281bff5db3f0695f018cb/Makefile"&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;MARKDOWN&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;wildcard ./**/*.md&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;README&lt;/span&gt;.&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="n"&gt;rss&lt;/span&gt;.&lt;span class="n"&gt;xml&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;blog&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; *.&lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt;.*
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        go build
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;README.md&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;MARKDOWN&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog &amp;gt; README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;rss.xml&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;MARKDOWN&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog -rss &amp;gt; rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;publish&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog -publish
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DAY&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;shell date&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;_PARSED_DAY&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;shell date &lt;span class="s1"&gt;&amp;#39;+%Y-%m-%d&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;$(DAY)&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        mkdir -p &lt;span class="s1"&gt;&amp;#39;$(_PARSED_DAY)&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TITLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;error TITLE is not defined&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./.scripts/gen-post.sh &lt;span class="s1"&gt;&amp;#39;$(_PARSED_DAY)&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(TITLE)&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;error FILE is not defined&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;draft&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(dir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mv &lt;span class="s1"&gt;&amp;#39;$(notdir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.$(notdir $(FILE))&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;undraft&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;undraft&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(dir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mv &lt;span class="s1"&gt;&amp;#39;$(notdir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(patsubst .%,%,$(notdir $(FILE)))&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        rm -rf blog
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For those unfamiliar with &lt;code&gt;Makefile&lt;/code&gt;, a quick explanation on how it works from
       +&lt;a href="https://en.wikipedia.org/wiki/Make_(software)#Makefile"&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Each rule begins with a &lt;em&gt;dependency line&lt;/em&gt; which consists of the rule's target
       +name followed by a colon (:) and optionally a list of targets on which the
       +rule's target depends, its prerequisites.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;So if we look for example at the &lt;code&gt;blog&lt;/code&gt; binary, the dependencies are all the
       +&lt;code&gt;.go&lt;/code&gt; files and Go module files like &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt;. We can make the
       +&lt;code&gt;blog&lt;/code&gt; binary by running:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make blog
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One nice thing about &lt;code&gt;Makefile&lt;/code&gt; is that they track if any of the source files
       +has a newer timestamp than the target file, and only trigger the build again if
       +there are changes, for example:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make blog
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;make: &amp;#39;blog&amp;#39; is up to date.
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; touch blog.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make blog
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But sometimes this property is undesirable. In those cases we can declare a
       +target as &lt;code&gt;.PHONY&lt;/code&gt;, that basically instructs &lt;code&gt;Makefile&lt;/code&gt; to always make the
       +target. One classic example is &lt;code&gt;clean&lt;/code&gt; target, that removes build artifacts:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make clean
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;rm -rf blog
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make clean
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;rm -rf blog
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By the way, it is better to declare a target as &lt;code&gt;.PHONY&lt;/code&gt; than declaring
       +dependencies incorrectly, especially in languages that has fast build times
       +like e.g.: Go. The worst thing that can happen is something not being rebuild
       +when it needs to. So my recomendation if you are writing your first &lt;code&gt;Makefile&lt;/code&gt;
       +is to just declare everything as &lt;code&gt;.PHONY&lt;/code&gt;. You can always improve it later.&lt;/p&gt;
       +&lt;p&gt;One last basic concept that I want to explain about &lt;code&gt;Makefile&lt;/code&gt; is the default
       +target: it is the target that is run if you just run &lt;code&gt;make&lt;/code&gt; without arguments
       +inside the directory that contains a &lt;code&gt;Makefile&lt;/code&gt;. The default target is
       +generally the first target in the &lt;code&gt;Makefile&lt;/code&gt;. It is common to have an &lt;code&gt;all&lt;/code&gt;
       +target (that is also marked as &lt;code&gt;.PHONY&lt;/code&gt;) that has as dependencies all the
       +targets that you want to build by default. In this particular case I declare
       +the &lt;code&gt;README.md&lt;/code&gt; and &lt;code&gt;rss.xml&lt;/code&gt; files to be build by default, and they themselves
       +depends in &lt;code&gt;blog&lt;/code&gt; binary being build. So once I run &lt;code&gt;make&lt;/code&gt; you get as result:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog &amp;gt; README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog -rss &amp;gt; rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And this result above highlights the first reason I think you should have a
       +&lt;code&gt;Makefile&lt;/code&gt; or something similar in your projects: you don't need to remember
       +the exactly steps that you need to get things working. If I see one project of
       +mine having a &lt;code&gt;Makefile&lt;/code&gt;, I can be reasonably confident that I can get it
       +working by just running &lt;code&gt;make&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;But now let's focus in the other targets that I have in the &lt;code&gt;Makefile&lt;/code&gt; that are
       +not related to the build process but are there to help me manage my blog posts.
       +Remember the rules I explained above? Maybe not, but it should be no problem,
       +because:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ make post TITLE=&amp;quot;My new blog post&amp;quot;
       +mkdir -p &amp;quot;2024-08-13&amp;quot;
       +./.scripts/gen-post.sh &amp;quot;2024-08-13&amp;quot; &amp;quot;My new blog post&amp;quot;
       +Creating file: 2024-08-13/02-my-new-blog-post.md
       +&lt;p&gt;$ cat 2024-08-13/02-my-new-blog-post.md&lt;/p&gt;
       +&lt;h1&gt;My new blog post&lt;/h1&gt;
       +&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
       +&lt;p&gt;This command, &lt;code&gt;make post&lt;/code&gt;, is responsible for:&lt;/p&gt;
       +&lt;ol&gt;
       +&lt;li&gt;Create a new directory for today, if it doesn't exist&lt;/li&gt;
       +&lt;li&gt;Run the
       +&lt;a href="https://github.com/thiagokokada/blog/blob/6a3b06970729f7650e5bee5fb0e1f9f2541ffea8/.scripts/gen-post.sh"&gt;&lt;code&gt;gen-post.sh&lt;/code&gt;&lt;/a&gt;
       +script, that:
       +&lt;ol&gt;
       +&lt;li&gt;Enumerates all posts from the day, so we can number the new post correctly
       +&lt;ul&gt;
       +&lt;li&gt;We already had this post planned for 2024-08-13, so the new post is 02&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;Slugify the title, so we can create each Markdown file with the correct
       +filename&lt;/li&gt;
       +&lt;li&gt;Creates a new Markdown file with the title as a &lt;code&gt;h1&lt;/code&gt; header&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;p&gt;The steps above may or may not seen trivial, and for a while I was doing them
       +manually. But not having to think what is the current date or if I already
       +posted that day or what is the slug is for the title make (pun intended) my
       +like much easier.&lt;/p&gt;
       +&lt;p&gt;Yes, the code is ugly. The way variables works in &lt;code&gt;Make&lt;/code&gt; is that you can
       +declare then inside the &lt;code&gt;Makefile&lt;/code&gt;, but they can be overwritten in the terminal
       +if you pass them. I used this to allow &lt;code&gt;make post&lt;/code&gt; to also work for future
       +posts:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make post &lt;span class="nv"&gt;TITLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Another new blog post&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;DAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2024-12-12
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mkdir -p &amp;#34;2024-12-12&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./.scripts/gen-post.sh &amp;#34;2024-12-12&amp;#34; &amp;#34;Another new blog post&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Creating file: 2024-12-12/01-another-new-blog-post.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So in the above case, &lt;code&gt;DAY&lt;/code&gt; is filled with the value passed in the terminal
       +instead of default (that would be the current day), and &lt;code&gt;_PARSED_DAY&lt;/code&gt; is the
       +day we use to actually create the directory. We can actually pass any date
       +format recognised by
       +&lt;a href="https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html"&gt;&lt;code&gt;date&lt;/code&gt;&lt;/a&gt;,
       +not just &lt;code&gt;YYYY-MM-DD&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;I have 2 other phony targets that I want to talk, &lt;code&gt;draft&lt;/code&gt; and &lt;code&gt;undraft&lt;/code&gt;. They
       +expect a &lt;code&gt;FILE&lt;/code&gt; to be passed, and I use them to either hide or unhide a file:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make draft &lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2024-12-12/01-another-new-blog-post.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mv &amp;#34;2024-12-12/01-another-new-blog-post.md&amp;#34; &amp;#34;2024-12-12/.01-another-new-blog-post.md&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make undraft &lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2024-12-12/.01-another-new-blog-post.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mv &amp;#34;2024-12-12/.01-another-new-blog-post.md&amp;#34; &amp;#34;2024-12-12/01-another-new-blog-post.md&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Why? Because hidden files are &lt;a href="https://github.com/thiagokokada/blog/blob/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7/blog.go#L101-L104"&gt;explicit
       +ignored&lt;/a&gt;
       +during my directory parser to mean they're a draft post and not ready to be
       +published. And the reason I created those targets is because I was tired of
       +trying to hide or unhide a file manually.&lt;/p&gt;
       +&lt;p&gt;So that's it, for the same reason you &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;should probably automate
       +everything&lt;/a&gt;,
       +you also need to have some way to automate your tasks. &lt;code&gt;Makefile&lt;/code&gt; is one way to
       +do it, maybe not the best way to do it, but it works and it is available
       +anywhere.&lt;/p&gt;</summary></entry><entry><title>Things I don't like in my Chromebook Duet 3</title><link href="https://kokada.dev/blog/things-i-dont-like-in-my-chromebook-duet-3/" rel="alternate"/><published>2024-08-12T00:00:00+00:00</published><id>https://kokada.dev/blog/things-i-dont-like-in-my-chromebook-duet-3/</id><summary type="html">&lt;p&gt;So this is kind of a continuation from my &lt;a href="https://kokada.dev/blog/my-favorite-device-is-a-chromebook"&gt;previous
       +post&lt;/a&gt; talking why
       +my favorite device is a Chromebook. In this post I want to talk about what
       +makes me this device unhappy, and comment about things that if changed would
       +make it a much better device.&lt;/p&gt;
       +&lt;p&gt;But before talking about the negative aspects, let me talk about a positive
       +aspect that I just briefly talked in the previous post: the screen. It is a
       +HiDPI screen (2000x1200 resolution in 10.95''), that is unexpected bright (400
       +nits according to the
       +&lt;a href="https://www.lenovo.com/us/en/p/laptops/lenovo/lenovo-edu-chromebooks/ideapad-duet-3-chromebook-11-inch,-qlc/len101i0034"&gt;specs&lt;/a&gt;).
       +It is difficult to find laptops at the same price with a screen that good. At
       +10.95'' in its default resolution I find it too small (like 1250x750), but I
       +find the font size acceptable at 115% scale (1087x652). Yes, it result in a
       +small workspace, but this is not a big issue for what I do in this device. It
       +is also only 60Hz, but I thought I would miss high refresh rate more than I
       +actually miss in this device.&lt;/p&gt;
       +&lt;p&gt;Update: I forgot to say one thing about the screen: it scratches really easy. I
       +got my screen scratched after the first day of usage, and considering the price
       +I don't think the screen has a hardened glass. I bought a cheap glass screen
       +protector and this did the trick though, even hiding the previous scratch, and
       +I have zero issues with the screen now.&lt;/p&gt;
       +&lt;p&gt;Now the first aspect that I don't like: the speakers. They sound tiny and even
       +at maximum volume it is not really loud. The speakers is the only reason why I
       +still keep my &lt;a href="https://www.gsmarena.com/xiaomi_pad_5-11042.php"&gt;Xiaomi Pad 5&lt;/a&gt;,
       +because I like to watch animes/videos before sleep and having good speakers is
       +a must.&lt;/p&gt;
       +&lt;p&gt;The keyboard has that issue that I mentioned in the previous post: sometimes
       +the key get stuck, and I get duplicated characters. But it also has some minor
       +issues that I didn't talked about: the first one is the UK layout that has some
       +extra keys that I have no use for, but this also makes the keys that I use
       +smaller. Very much a &amp;quot;me&amp;quot; problem, since if I had got a US version I wouldn't
       +have those issues, but an issue nonetheless that gets worse considering how
       +small the keyboard is. I am actually suprised how fast I can type considering
       +how many issues this keyboard has, so maybe this is a testament that this
       +keyboard is not actually that bad.&lt;/p&gt;
       +&lt;p&gt;The other keyboard issue is a problem that affects all Chromebooks: its custom
       +layout. Google replaced a few keys like Fn keys with shortcuts and replaced the
       +Caps Lock with a
       +&lt;a href="https://chromeunboxed.com/chromebook-launcher-now-everything-button"&gt;&amp;quot;Everything&amp;quot;&lt;/a&gt;
       +key (that is similar to the Windows Key), while removing Windows Key from its
       +place. I actually have less issue with this than I initially though: I don't
       +care too much about Fn keys (except when using IntelliJ, but that is something
       +that I only use at &lt;code&gt;$CURRENT_JOB&lt;/code&gt;), and ChromeOS is surprisingly powerful in
       +its customisation, allowing you to swap key functionality. I remap Everything
       +key with Esc, and Esc for the Everything key, and I can get productive in my
       +&lt;code&gt;neovim&lt;/code&gt; setup.&lt;/p&gt;
       +&lt;p&gt;And finally, let me talk more about the performance: yes, it is bad, but
       +bearable once you get used to. The issue is both the CPU and IO. While the CPU,
       +a &lt;a href="https://www.qualcomm.com/products/mobile/snapdragon/laptops-and-tablets/snapdragon-mobile-compute-platforms/snapdragon-7c-gen-2-compute-platform"&gt;Snapdragon 7c Gen
       +2&lt;/a&gt;
       +is octa-core, it has only 2 high performance CPU cores vs 6 low performance
       +ones (2xARM Cortex A76 vs 6xARM Cortex A55). If it was something like 4x4, it
       +would be much better. The fact that the cores are old doesn't help either.&lt;/p&gt;
       +&lt;p&gt;But the worst part is the IO. Not only it uses a eMMC module, it is slow:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-12/Screenshot_2024-08-12_20.50.42.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-12/Screenshot_2024-08-12_20.50.42.png" alt="CPDT Benchmark results from Chromebook Duet 3."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;I don't know how much more expensive it would be to put a
       +&lt;a href="https://en.wikipedia.org/wiki/Universal_Flash_Storage"&gt;UFS&lt;/a&gt; instead of eMMC in
       +this device, but this is probably the choice that would most increase
       +performance in this device, especially considering how aggressive Chromebooks
       +use (z)swap.&lt;/p&gt;
       +&lt;p&gt;Update 2: I forgot to talk about the fact that the exterior of the device is
       +covered in cloth. I thought I would hate this at first, but nowadays I kind
       +like it. And it is also nice that it will never get scratched, I don't care too
       +much about the exterior of this device and it is the only device that I have at
       +home that doesn't have additional protection (except the screen protector
       +mentioned above).&lt;/p&gt;</summary></entry><entry><title>Building static binaries in Nix</title><link href="https://kokada.dev/blog/building-static-binaries-in-nix/" rel="alternate"/><published>2024-08-11T00:00:00+00:00</published><id>https://kokada.dev/blog/building-static-binaries-in-nix/</id><summary type="html">&lt;p&gt;I recently had laser eye surgery for my near-sightedness, and while evaluating
       +if I could have the surgery I discovered that I was suffering from dry eyes.
       +Thanks to this, my ophthalmologist recommended that every 20 minutes while
       +using screens, I look somewhere 20 feet away for 20 seconds, a technique known
       +as &lt;a href="https://www.healthline.com/health/eye-health/20-20-20-rule"&gt;20-20-20 rule&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;I had issues following this rule because I never remembered to do the pauses. I
       +initially tried to setup an alarm, but this became annoying, so I decided to
       +try to find a program. I found
       +&lt;a href="https://tonyh4156.github.io/20-20-20.github.io/"&gt;this&lt;/a&gt; one for macOS that
       +worked fine, but it bothered me that it was compiled for x86_64 while I was
       +using a M1 MacBook Pro at the time, and also I needed something that worked in
       +Linux.&lt;/p&gt;
       +&lt;p&gt;Since I never found a good multi-platform alternative, I decided to write my
       +own. This became
       +&lt;a href="https://github.com/thiagokokada/twenty-twenty-twenty/"&gt;twenty-twenty-twenty&lt;/a&gt;,
       +the first &lt;a href="https://kokada.dev/blog/go-a-reasonable-good-language"&gt;Go&lt;/a&gt; program
       +that I ever wrote. I wrote it in Go because I wanted to learn the language, but
       +also because Go made it easy to build static binaries. And the first version I
       +could build static binaries without issues because I was using
       +&lt;a href="https://github.com/gen2brain/beeep"&gt;beeep&lt;/a&gt;, that uses pure Go code in all
       +supported platforms. However, it also meant that the notifications in macOS
       +looked ugly, since it used
       +&lt;a href="https://github.com/gen2brain/beeep/blob/master/beep_darwin.go#L20"&gt;osascript&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;I wanted better integration with macOS, so this meant switching libraries.
       +After searching for a while, the
       +&lt;a href="https://pkg.go.dev/gioui.org/x/notify"&gt;notify&lt;/a&gt; library from
       +&lt;a href="https://gioui.org/"&gt;GioUI&lt;/a&gt; is the one that seemed to work better. It
       +implements notification in macOS using its native framework, so it works much
       +better, but sadly it meant losing static binaries because it depends in CGO.&lt;/p&gt;
       +&lt;p&gt;Not a big loss initially, because I am only depending in Foundation inside
       +macOS (that should always be available), and in Linux I could still statically
       +compile. However I eventually added more features like sound
       +(via &lt;a href="https://github.com/gopxl/beep"&gt;beep&lt;/a&gt;) and tray icon (via
       +&lt;a href="https://github.com/fyne-io/systray"&gt;systray&lt;/a&gt;), that meant I needed CGO in both
       +macOS and Linux.&lt;/p&gt;
       +&lt;p&gt;Losing static binaries in Linux is a much bigger deal, since Linux is a moving
       +target. The general recommendation for building CGO binaries statically is
       +using
       +&lt;a href="https://eli.thegreenplace.net/2024/building-static-binaries-with-go-on-linux/"&gt;musl&lt;/a&gt;,
       +but this also means building all dependencies that we need using musl (e.g.:
       +&lt;a href="https://github.com/ebitengine/oto?tab=readme-ov-file#linux"&gt;&lt;code&gt;ALSA&lt;/code&gt;&lt;/a&gt; for
       +[beep/oto]). This generally means pain, but Nix makes it easy.&lt;/p&gt;
       +&lt;p&gt;Let's start by creating a &lt;a href="https://github.com/thiagokokada/twenty-twenty-twenty/blob/main/twenty-twenty-twenty.nix"&gt;Nix
       +file&lt;/a&gt;
       +that builds our Go module (simplified below for brevity):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdenv&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alsa-lib&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buildGoModule&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pkg-config&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;buildGoModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanSource&lt;/span&gt; &lt;span class="sr"&gt;./.&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;vendorHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sha256-NzDhpJRogIfL2IYoqAUHoPh/ZdNnvnhEQ+kn8A+ZyBw=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;CGO_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optionals&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLinux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkg-config&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optionals&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLinux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;alsa-lib&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;ldflags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-X=main.version=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-s&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-w&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optionals&lt;/span&gt; &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-linkmode external&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;-extldflags &amp;#34;-static&amp;#34;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alerts every 20 minutes to look something at 20 feet away for 20 seconds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/thiagokokada/twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;licenses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;mainProgram&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we can build it with the following &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nixpkgs/nixpkgs-unstable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;flake-compat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:edolstra/flake-compat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;supportedSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aarch64-linux&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# Helper function to generate an attrset &amp;#39;{ x86_64-linux = f &amp;#34;x86_64-linux&amp;#34;; ... }&amp;#39;.&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;genAttrs&lt;/span&gt; &lt;span class="n"&gt;supportedSystems&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# Nixpkgs instantiated for supported system types.&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;nixpkgsFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nixpkgsFor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;twenty-twenty-twenty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;twenty-twenty-twenty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callPackage&lt;/span&gt; &lt;span class="sr"&gt;./twenty-twenty-twenty.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;twenty-twenty-twenty-static&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgsStatic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callPackage&lt;/span&gt; &lt;span class="sr"&gt;./twenty-twenty-twenty.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;});&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I think this shows how powerful Nix is: the only difference between the normal
       +build and a static build the usage of &lt;code&gt;pkgs.pkgsStatic&lt;/code&gt; instead of &lt;code&gt;pkgs&lt;/code&gt;. This
       +automatically builds all packages statically with &lt;code&gt;musl&lt;/code&gt;. Also we need pass
       +some &lt;a href="https://honnef.co/articles/statically-compiled-go-programs-always-even-with-cgo-using-musl/"&gt;extra
       +flags&lt;/a&gt;
       +to the Go compiler (i.e.: &lt;code&gt;-linkmode external -extldflags &amp;quot;-static&amp;quot;&lt;/code&gt;), but this
       +is a requirement from Go.&lt;/p&gt;
       +&lt;p&gt;So, does it work? Let's test:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#twenty-twenty-twenty-static
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; file result/bin/twenty-twenty-twenty
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;result/bin/twenty-twenty-twenty: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./result/bin/twenty-twenty-twenty
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/ly9d7llymzjyf6gi1455qzayqipk2kab-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/ly9d7llymzjyf6gi1455qzayqipk2kab-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/ly9d7llymzjyf6gi1455qzayqipk2kab-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2024-08-11T19:26:33+01:00 INFO Running twenty-twenty-twenty every 20.0 minute(s), with 20 second(s) duration and sound set to true
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are some warns and sadly the sound doesn't work. I think the issue is
       +related because of my usage of PipeWire and the binary may work in a pure ALSA
       +system, but I don't have access to one. Maybe adding &lt;code&gt;pipewire&lt;/code&gt; to
       +&lt;code&gt;buildInputs&lt;/code&gt; would fix this issue, but I can't get &lt;code&gt;pipewire&lt;/code&gt; to be compiled
       +statically (because of its dependencies). I think this is a good show how easy
       +it is to statically compilation is in Nix, but also how complex static binaries
       +are to get correctly.&lt;/p&gt;
       +&lt;p&gt;Bonus points for
       +&lt;a href="https://nix.dev/tutorials/cross-compilation.html"&gt;cross-compilation&lt;/a&gt;. We can
       +easily cross-compile by using &lt;code&gt;pkgsCross&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nixpkgsFor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;twenty-twenty-twenty-static-aarch64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgsCross&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aarch64-multiplatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgsStatic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callPackage&lt;/span&gt; &lt;span class="sr"&gt;./twenty-twenty-twenty.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;});&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea of &lt;code&gt;pkgsCross&lt;/code&gt; is to select a target platform (e.g.:
       +&lt;code&gt;aarch64-multiplatform&lt;/code&gt;) and use it as any other &lt;code&gt;pkgs&lt;/code&gt;. We can even chain
       +&lt;code&gt;pkgsStatic&lt;/code&gt; to statically cross compile binaries:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#twenty-twenty-twenty-static-aarch64
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; file result/bin/twenty-twenty-twenty
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;result/bin/twenty-twenty-twenty: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</summary></entry><entry><title>Meta: are quick bits really quick?</title><link href="https://kokada.dev/blog/meta-are-quick-bits-really-quick/" rel="alternate"/><published>2024-08-07T00:00:00+00:00</published><id>https://kokada.dev/blog/meta-are-quick-bits-really-quick/</id><summary type="html">&lt;p&gt;When I wrote my first &lt;a href="https://kokada.dev/blog/quick-bits-nix-shell-is-cursed"&gt;quick
       +bits&lt;/a&gt; post in this
       +blog, I gave that title prefix without much thought: I knew it was supposed to
       +be a quicker post than my previous one, but I never thought too deeply about
       +it. But after:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah **/*quick-bits*.md &lt;span class="p"&gt;|&lt;/span&gt; wc -l
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;4
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, 4 blog posts starting with the same prefix, I was curious: are those
       +quick bits really quick, or at least quicker? Let's see:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ wc -w **/*.md
       + 1107 2024-07-26/01-writing-nixos-tests-for-fun-and-profit.md
       + 1220 2024-07-26/02-using-github-as-a-bad-blog-platform.md
       +  286 2024-07-27/01-quick-bits-nix-shell-is-cursed.md
       +  387 2024-07-29/01-quick-bits-why-you-should-automate-everything.md
       + 1060 2024-07-29/02-go-a-reasonable-good-language.md
       + 1380 2024-07-30/01-first-impressions-fpgbc.md
       + 1238 2024-07-31/01-generating-yaml-files-with-nix.md
       + 2308 2024-08-01/01-troubleshooting-zsh-lag-and-solutions-with-nix.md
       +  504 2024-08-01/02-quick-bits-realise-nix-symlinks.md
       +  834 2024-08-04/01-make-nixd-modules-completion-work-anywhere-with-flakes.md
       + 1147 2024-08-05/01-my-favorite-device-is-a-chromebook.md
       +  394 2024-08-07/01-quick-bits-is-crostini-a-microvm.md
       +  120 README.md
       +11985 total
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;While using &lt;code&gt;wc&lt;/code&gt; is probably not the best way to measure word count (especially
       +in this blog, since I tend to write lots of code snippets), I think this at
       +least it gives me a good insight: yes, quick bits are quicker, and they're
       +basically posts with a soft limit around 500 words. So expect in future this
       +limit to be used.&lt;/p&gt;
       +&lt;p&gt;By the way, at:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; wc -w 2024-08-07/02-meta-are-quick-bits-really-quick.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;220 2024-08-07/02-meta-are-quick-bits-really-quick.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This post is also technically a quick bits post, but &amp;quot;quick bits meta&amp;quot; would be
       +too much. And yes, that last block of code is also meta ;).&lt;/p&gt;</summary></entry><entry><title>Quick bits: is Crostini a micro VM?</title><link href="https://kokada.dev/blog/quick-bits-is-crostini-a-micro-vm/" rel="alternate"/><published>2024-08-07T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-is-crostini-a-micro-vm/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I am not a Virtual Machine specialist, this post is mainly the
       +conclusion I got after some searching.&lt;/p&gt;
       +&lt;p&gt;Someone asked me in my &lt;a href="https://kokada.dev/blog/my-favorite-device-is-a-chromebook"&gt;previous
       +post&lt;/a&gt; about my
       +Chromebook if Crostini could be considered a micro VM. This was a interesting
       +question, so I decided to do another post.&lt;/p&gt;
       +&lt;p&gt;To start, it is really difficult to get a good definition of what a &amp;quot;micro VM&amp;quot;
       +is. &lt;a href="https://firecracker-microvm.github.io/"&gt;Firecracker&lt;/a&gt; defines itself as a
       +micro VM, and describes itself in its website as:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Firecracker is a virtual machine monitor (VMM) that uses the Linux
       +Kernel-based Virtual Machine (KVM) to create and manage microVMs. Firecracker
       +has a minimalist design. It excludes unnecessary devices and guest
       +functionality to reduce the memory footprint and attack surface area of each
       +microVM. This improves security, decreases the startup time, and increases
       +hardware utilization.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Now looking at Crostini, its heart is a VMM called
       +&lt;a href="https://crosvm.dev/"&gt;crosvm&lt;/a&gt;. It is described in its
       +&lt;a href="https://chromium.googlesource.com/chromiumos/platform/crosvm/+/HEAD/README.md"&gt;README&lt;/a&gt;
       +as:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;crosvm is a virtual machine monitor (VMM) based on Linux’s KVM hypervisor,
       +with a focus on simplicity, security, and speed. crosvm is intended to run
       +Linux guests, originally as a security boundary for running native
       +applications on the ChromeOS platform. Compared to QEMU, crosvm doesn’t
       +emulate architectures or real hardware, instead concentrating on
       +paravirtualized devices, such as the virtio standard.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Similar descriptions right? Actually Firecracker website says it &amp;quot;started from
       +Chromium OS's Virtual Machine Monitor, crosvm, an open source VMM written in
       +Rust&amp;quot;. So I would say it is safe to say crosvm itself is a micro VM.&lt;/p&gt;
       +&lt;p&gt;But
       +&lt;a href="https://www.chromium.org/chromium-os/developer-library/guides/containers/containers-and-vms/"&gt;Crostini&lt;/a&gt;
       +itself is a combination of virtualization AND containerization. Basically
       +inside the VM it runs a Linux kernel and &lt;a href="https://linuxcontainers.org/"&gt;LXC&lt;/a&gt;,
       +that can start arbitrary containers inside it. From the Crostini documentation
       +this choice seems to be to keep startup times down, and also to increase
       +security (e.g.: in case of a security issue inside the container).&lt;/p&gt;
       +&lt;p&gt;This is definitely an interesting choice, since containers allow the overhead
       +of each distro that you run inside Crostini to be low, and the main VM itself
       +(called
       +&lt;a href="https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/HEAD/project-termina/"&gt;Termina&lt;/a&gt;)
       +should have low overhead too thanks to crosvm.&lt;/p&gt;
       +&lt;p&gt;By the way, if you want to learn more how &amp;quot;devices&amp;quot; works inside a micro VM
       +like crosvm, I recommend &lt;a href="https://prilik.com/blog/post/crosvm-paravirt/"&gt;this blog
       +post&lt;/a&gt; talking about
       +paravirtualized devices in crosvm.&lt;/p&gt;</summary></entry><entry><title>My favorite device is a Chromebook</title><link href="https://kokada.dev/blog/my-favorite-device-is-a-chromebook/" rel="alternate"/><published>2024-08-05T00:00:00+00:00</published><id>https://kokada.dev/blog/my-favorite-device-is-a-chromebook/</id><summary type="html">&lt;p&gt;Most of the posts in this blog (including this one) and most of I would call
       +&amp;quot;personal computing&amp;quot; that I do nowadays is mostly done in one of the most
       +unremarkable devices that I own: a cheap &lt;a href="https://www.lenovo.com/us/en/p/laptops/lenovo/lenovo-edu-chromebooks/ideapad-duet-3-chromebook-11-inch,-qlc/len101i0034"&gt;Chromebook Duet
       +3&lt;/a&gt;,
       +that I bought for around EUR300. I was thinking why, because it is woefully
       +underpowered: a &lt;a href="https://www.anandtech.com/show/16696/qualcomm-announces-snapdragon-7c-gen-2-entrylevel-pc-and-chromebook-refresh"&gt;Snapdragon 7c Gen
       +2&lt;/a&gt;,
       +a CPU that was already considered slow 3 years ago, coupled with an eMMC for
       +storage, that is not much faster than a HDD. At least I have the 8GB RAM
       +version instead of the 4GB one.&lt;/p&gt;
       +&lt;p&gt;It is a hybrid device, one that can be used as either a tablet or laptop, but
       +it is compromised experience in both cases: as a tablet, it lacks the better
       +touch optimised interface from iOS or Android; as a laptop, you have to depend
       +on the stand to adjust the screen, and the detachable keyboard is worse than
       +any laptop I have ever owned: getting keys stucked and characters being
       +duplicated as a result is a common occurence. It is not so bad that I can't get
       +things done though. About the trackpad: its biggest quality is that I never
       +feel the need to use the touchscreen in laptop mode, that is to say that it is
       +acceptable. Just crank up the pointer speed in ChromeOS settings, otherwise you
       +never get anywhere since the trackpad is so small. There is also an active
       +stylus, that helped me sometimes when I needed to sign something but otherwise
       +I can't comment too much.&lt;/p&gt;
       +&lt;p&gt;But I really love this device. It is generally the only device that I bring in
       +trips nowadays, because while it is compromised it works well enough: I can use
       +to consume media in tablet mode (the fact that ChromeOS supports Android apps
       +is a plus in those cases), browse the web and even do Linux stuff (more about
       +this later). The fact that it is small (the size remembers me of a
       +&lt;a href="https://en.wikipedia.org/wiki/Netbook"&gt;netbook&lt;/a&gt;), lightweight (~1KG, including
       +the keyboard), has a good screen (that is bright and HiDPI) and good battery
       +life (I don't have numbers but I almost never worry about it) is what makes
       +this device the perfect companion to trips.&lt;/p&gt;
       +&lt;p&gt;Also, it has 2 USB-C ports and supports DisplayPort alt-mode, so it means you
       +can charge it, connect to a external display and peripherals, all at the same
       +time. Sadly, the maximum output resolution I got was 1080p (2560x1080),
       +although some people at Reddit &lt;a href="https://www.reddit.com/r/chromeos/comments/zh27tg/comment/izku724/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button"&gt;seems to have
       +success&lt;/a&gt;
       +at 1440p, and the specs suggests it supports 4k. It may be my Dell S3423DWC
       +monitor being wonky, the fact that it is Ultrawide or the cable, who knows? I
       +even tried to change the monitor to &amp;quot;High Resolution&amp;quot; mode in settings, but to
       +no avail.&lt;/p&gt;
       +&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; looking at the &lt;a href="https://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/prod_brief_qcom_sd7c_gen2.pdf"&gt;product
       +brief&lt;/a&gt;
       +for Snapdragon 7c Gen 2, it seems it supports up to QHD@60Hz (1440p) for the
       +external display, and not 4k. This explains why it doesn't work at maximum
       +resolution in my Dell S3423DWC, since while it is 1440p it is Ultrawide, so the
       +resolution is bigger than QHD (3440x1440 vs 2560x1440).&lt;/p&gt;
       +&lt;p&gt;ChromeOS is also really interesting nowadays. To start, it is designed from the
       +ground up to be a &lt;a href="https://support.google.com/chromebook/answer/3438631"&gt;secure computing
       +environment&lt;/a&gt;, probably
       +the most secure OS for consumers right now. Being a Chrome-first OS makes it a
       +compromised experience, for example, it is the only device that I use Chrome as
       +my main browser (since I personally prefer Firefox). But having a OS that boots
       +fast is great: I never worry about OS updates because I know the device will be
       +ready in seconds after a reboot. And the whole desktop experience inside the
       +ChromeOS desktop is good, having shortcuts for many operations so you can get
       +things done fast, and support for virtual desktops (ChromeOS call it &amp;quot;desks&amp;quot;)
       +means you can organise your windows as much as you want.&lt;/p&gt;
       +&lt;p&gt;And what I think makes ChromeOS really powerful is
       +&lt;a href="https://chromeos.dev/en/linux"&gt;Crostini&lt;/a&gt;, a full Linux VM that you can run
       +inside ChromeOS. It runs Debian (it seems you can &lt;a href="https://www.reddit.com/r/Crostini/wiki/howto/run-other-distros/"&gt;run other
       +distros&lt;/a&gt;
       +though) with a deep integration with ChromeOS, so you can run even graphical
       +programs without issues (including OpenGL!):&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.22.29.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.22.29.png" alt="Fastfetch inside Crostini with gitk running side-by-side."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.39.58.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.39.58.png" alt="Running glxgears inside Crostini."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;This is all thanks to
       +&lt;a href="https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/vm_tools/sommelier/README.md"&gt;Sommelier&lt;/a&gt;,
       +a nested Wayland compositor that runs inside Crostini and allow both Wayland
       +and X11 applications to be forwarded to ChromeOS. The integration is so good
       +that I can run Firefox inside Crostini and works well enough, but sadly Firefox
       +is too slow in this device (I am not sure if the issue is ChromeOS or Firefox,
       +but I suspect the later since Google does some optimisation per device).&lt;/p&gt;
       +&lt;p&gt;One interesting tidbit about the OpenGL situation in this device: this seems to
       +be the first Chromebook to ship with open source drivers, thanks to Freedreno.
       +There is &lt;a href="https://www.youtube.com/watch?v=8mnjSmN03VM"&gt;this&lt;/a&gt; very interesting
       +presentation done by Rob Clark in XDC 2021, that I recommended anyone
       +interested in free drivers to watch (the reference design of Duet 3 is
       +&lt;a href="https://chromeunboxed.com/chromebook-tablet-snapdragon-7c-homestar-coachz-strongbad"&gt;Strongbad&lt;/a&gt;).&lt;/p&gt;
       +&lt;p&gt;The Crostini integration is probably the best VM integration with Linux I ever
       +saw in an OS: you can manage files inside the VM, share directories between the
       +OS and VM, copy and paste works between the two, GUI applications installed
       +inside the VM appear in the ChromeOS menu, memory allocation inside the VM is
       +transparent, etc. Even the themes for Linux GUI applications are customised to
       +match ChromeOS. It is unironically one of the best Linux desktop experiences I
       +ever had.&lt;/p&gt;
       +&lt;p&gt;Of course I am using Nix, but since the Crostini integration depends on some
       +services being configured and installed, I decided to run Nix inside Debian
       +instead of NixOS and run &lt;a href="https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone"&gt;Home-Manager
       +standalone&lt;/a&gt;.
       +I recommend checking the official &lt;a href="https://wiki.nixos.org/wiki/Installing_Nix_on_Crostini"&gt;NixOS Wiki article about
       +Crostini&lt;/a&gt;, that details
       +how to register applications in ChromeOS (so desktop applications appear in
       +menu) and use &lt;a href="https://github.com/nix-community/nixGL"&gt;nixGL&lt;/a&gt; to make OpenGL
       +applications work.&lt;/p&gt;
       +&lt;p&gt;Like I said at the start of the article, the device is woefully slow thanks to
       +its CPU and eMMC. It does mean that, for example, activating my Home-Manager
       +configuration takes a while (around 1 minute, vs a few seconds in my laptop).
       +But it is much faster than say,
       +&lt;a href="https://github.com/nix-community/nix-on-droid-app"&gt;nix-on-droid&lt;/a&gt;, that the
       +last time I tried in a much more powerful device (&lt;a href="https://www.gsmarena.com/xiaomi_pad_5-11042.php"&gt;Xiaomi Pad
       +5&lt;/a&gt;), took 30 minutes until I
       +just decided to cancel the operation. Having a proper VM instead of
       +&lt;a href="https://wiki.termux.com/wiki/PRoot"&gt;proot&lt;/a&gt; makes all the difference here.&lt;/p&gt;
       +&lt;p&gt;I can even do some light programming here: using my
       +&lt;a href="https://kokada.dev/blog/troubleshoting-zsh-lag-and-solutions-with-nix"&gt;ZSH&lt;/a&gt;
       +and neovim configuration (including LSP for coding) is reasonable fast. For
       +example, I did most of the code that &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;powers this
       +blog&lt;/a&gt;
       +using this Chromebook. If I need more power, I can use the &lt;a href="https://tailscale.com/kb/1267/install-chromebook"&gt;Tailscale app for
       +Android&lt;/a&gt; to connect to any
       +other of my hosts via SSH. Yes, the Tailscale app works in Crostini, sadly
       +without MagicDNS, so you need to use the internal Tailscale IPs instead.&lt;/p&gt;
       +&lt;p&gt;Until Google decides to give us a proper VM or user namespaces in Android or
       +release a hybrid Chromebook device with better specs, this small Chromebook
       +will probably stay as my travel companion, and is one of my favorite devices.&lt;/p&gt;</summary></entry><entry><title>Make nixd module completion to work anywhere (with Flakes)</title><link href="https://kokada.dev/blog/make-nixd-module-completion-to-work-anywhere-with-flakes/" rel="alternate"/><published>2024-08-04T00:00:00+00:00</published><id>https://kokada.dev/blog/make-nixd-module-completion-to-work-anywhere-with-flakes/</id><summary type="html">&lt;p&gt;If you want the TL;DR, go to the bottom of the post (search for &amp;quot;final
       +result&amp;quot;).&lt;/p&gt;
       +&lt;p&gt;I recently switched from &lt;a href="https://github.com/oxalica/nil"&gt;nil&lt;/a&gt; to
       +&lt;a href="https://github.com/nix-community/nixd"&gt;nixd&lt;/a&gt; as my LSP of choice for Nix. I
       +was curious in &lt;code&gt;nixd&lt;/code&gt; for a long time since the fact that it can eval Nix code
       +means it can offer much more powerful completion than the &lt;code&gt;nil&lt;/code&gt;'s static
       +analysis, however it used to be difficult to setup. Nowadays it is much easier,
       +basically doing the right thing as long as you have &lt;code&gt;NIX_PATH&lt;/code&gt; setup, and you
       +get both package and NixOS modules completion.&lt;/p&gt;
       +&lt;p&gt;Getting Home-Manager modules though needs some setup. The recommended way to
       +setup accordingly to the &lt;a href="https://github.com/nix-community/nixd/blob/fe202307eaf7e89c4366ed927af761482a6065c8/nixd/docs/configuration.md"&gt;official
       +documentation&lt;/a&gt;
       +is to use the following for Flake based configurations (using neovim
       +configuration here, but it should be easy to adapt to other editors):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;import &amp;lt;nixpkgs&amp;gt; { }&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(builtins.getFlake (&amp;#34;git+file://&amp;#34; + toString ./.)).nixosConfigurations.miku-nixos.options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;home_manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(builtins.getFlake (&amp;#34;git+file://&amp;#34; + toString ./.)).homeConfigurations.home-linux.options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;-- ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works, but it should be pretty clear the limitations of using &lt;code&gt;./.&lt;/code&gt;: this
       +will only work if you open your editor in your &lt;a href="https://github.com/thiagokokada/nix-configs"&gt;Nix configuration
       +repository&lt;/a&gt;. For any other
       +repository, &lt;code&gt;nixosConfigurations.miku-nixos&lt;/code&gt; or &lt;code&gt;homeConfigurations.home-linux&lt;/code&gt;
       +will not exist and the completion will not work.&lt;/p&gt;
       +&lt;p&gt;It may look like this is easy to fix if you have
       +&lt;a href="https://nixos.org/manual/nixos/unstable/options#opt-_module.args"&gt;&lt;code&gt;specialArgs&lt;/code&gt;&lt;/a&gt;
       +(or &lt;code&gt;extraSpecialArgs&lt;/code&gt; in Home-Manager) set to have your
       +Flakes inputs, but:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# By the way, ${self} does not exist in the Flake output by default, you need&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# to explicit add `inherit self` to your outputs:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# https://discourse.nixos.org/t/who-is-self-in-flake-outputs/31859/4&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;nix-repl&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git+file://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;miku-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="n"&gt;while&lt;/span&gt; &lt;span class="n"&gt;calling&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;getFlake&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;builtin&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="err"&gt;«&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="err"&gt;»&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;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git+file://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;             &lt;span class="err"&gt;|&lt;/span&gt;  &lt;span class="err"&gt;^&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="n"&gt;while&lt;/span&gt; &lt;span class="n"&gt;evaluating&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="sd"&gt;git+file:///nix/store/avr1lcmznj8ghynh5vj1kakgfdf0zrxx-source&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;refer&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;store&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;such&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;avr1lcmznj8ghynh5vj1kakgfdf0zrxx-source&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, it was worth a try. Another option would be to:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:thiagokokada/nix-configs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;miku-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Or even something like this&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# However, using ${rev} means this wouldn&amp;#39;t work in dirty Flake repos, since&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ${rev} is not set in those cases&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:thiagokokada/nix-configs/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;miku-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But while it works, it is slow, because it needs network to evaluate (and it is
       +impure, since there is no &lt;code&gt;flake.lock&lt;/code&gt;).&lt;/p&gt;
       +&lt;p&gt;The default configuration for &lt;code&gt;nixd&lt;/code&gt; makes NixOS completion work even outside
       +of my configuration repo, and it is fast. How? I decided to take a look at the
       +&lt;code&gt;nixd&lt;/code&gt; source code and found
       +&lt;a href="https://github.com/nix-community/nixd/blob/d938026c55c7c36a6e79afd9627459160b4924ed/nixd/lib/Controller/LifeTime.cpp#L33C11-L35C76"&gt;this&lt;/a&gt;
       +(formatted here for legibility):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;&amp;lt;nixpkgs&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evalModules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;&amp;lt;nixpkgs/nixos/modules/module-list.nix&amp;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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentSystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Interesting, so they're manually loading the modules using &lt;code&gt;evalModules&lt;/code&gt;. As I
       +said above, it depends in &lt;code&gt;NIX_PATH&lt;/code&gt; being correctly set. Can we fix this to
       +use our Flake inputs instead? After some tries in the Nix REPL, I got the
       +following:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evalModules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/nixos/modules/module-list.nix&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentSystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we can adapt this to the neovim configuration:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(let pkgs = import &amp;#34;${inputs.nixpkgs}&amp;#34; { }; in (pkgs.lib.evalModules { modules =  (import &amp;#34;${inputs.nixpkgs}/nixos/modules/module-list.nix&amp;#34;) ++ [ ({...}: { nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This was easy. But the main issue is Home-Manager. How can we fix it? I needed
       +to take a look at the Home-Manager &lt;a href="https://github.com/nix-community/home-manager/blob/master/docs/default.nix#L161-L169"&gt;source
       +code&lt;/a&gt;
       +to find the answer:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;home-manager&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/modules/lib/stdlib-extended.nix&amp;#34;&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evalModules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;home-manager&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/modules/modules.nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The interesting part is: Home-Manager has its own extension of the module
       +system (including &lt;code&gt;evalModules&lt;/code&gt;). This includes e.g.: extra types used in
       +Home-Manager only. Also, we need to disable &lt;code&gt;checks&lt;/code&gt;, otherwise we will hit
       +some validations (e.g.: missing &lt;code&gt;stateVersion&lt;/code&gt;). I am not sure if this causes
       +any issue for module completion yet, I may set it in the future.&lt;/p&gt;
       +&lt;p&gt;And for the final result:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;import &amp;#34;${flake.inputs.nixpkgs}&amp;#34; { }&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(let pkgs = import &amp;#34;${inputs.nixpkgs}&amp;#34; { }; in (pkgs.lib.evalModules { modules =  (import &amp;#34;${inputs.nixpkgs}/nixos/modules/module-list.nix&amp;#34;) ++ [ ({...}: { nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;home_manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(let pkgs = import &amp;#34;${inputs.nixpkgs}&amp;#34; { }; lib = import &amp;#34;${inputs.home-manager}/modules/lib/stdlib-extended.nix&amp;#34; pkgs.lib; in (lib.evalModules { modules =  (import &amp;#34;${inputs.home-manager}/modules/modules.nix&amp;#34;) { inherit lib pkgs; check = false; }; })).options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yes, it is quite a mouthful, but it makes module completion work in any
       +repository, as long as you're using Flakes. And it is fast, since it doesn't
       +need any network access. Since we are already here, let's define &lt;code&gt;nixpkgs&lt;/code&gt; to
       +not depend in the &lt;code&gt;NIX_PATH&lt;/code&gt; being set too.&lt;/p&gt;</summary></entry><entry><title>Troubleshooting: ZSH lag and solutions with Nix</title><link href="https://kokada.dev/blog/troubleshoting-zsh-lag-and-solutions-with-nix/" rel="alternate"/><published>2024-08-01T00:00:00+00:00</published><id>https://kokada.dev/blog/troubleshoting-zsh-lag-and-solutions-with-nix/</id><summary type="html">&lt;p&gt;Inspired by this &lt;a href="https://lock.cmpxchg8b.com/slowterm.html"&gt;blog post from
       +Tavis&lt;/a&gt;, I decided to document my own
       +recent journey of reducing terminal (ZSH) lag startup. This post is way less
       +interesting than the one from Tavis that uses a debugger to patch applications
       +on the fly, but should still be interesting for some. And it also shows how
       +powerful Nix can be for some things.&lt;/p&gt;
       +&lt;p&gt;For context, I have basically 3 systems where I interact with terminal
       +frequently:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;&lt;a href="https://www.notebookcheck.net/Lenovo-ThinkPad-P14s-Gen-1-Laptop-Review-AMD-workstation-sans-dedicated-GPU.528746.0.html"&gt;Thinkpad P14s Gen 1&lt;/a&gt;
       +running NixOS, with a reasonable fast CPU (&lt;a href="https://www.notebookcheck.net/AMD-Ryzen-7-PRO-4750U-Processor-Benchmarks-and-Specs.464396.0.html"&gt;AMD Ryzen 7 PRO
       +4750U&lt;/a&gt;)
       +and disk (NVMe)&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-m1-pro-10-core-cpu-16-core-gpu-16-2021-specs.html"&gt;MacBook
       +Pro&lt;/a&gt;
       +with a really fast CPU (M1 Pro) and disk, but of course running macOS, being
       +phased-out since this is a job owned machine and I am changing jobs right
       +now, but should be replaced with another one soon™&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://chromeunboxed.com/lenovo-chromebook-duet-3-review-perfect-sequel"&gt;Chromebook Duet
       +3&lt;/a&gt;
       +running ChromeOS, with slow CPU (&lt;a href="https://www.notebookcheck.net/Qualcomm-Snapdragon-7c-Gen-2-Processor-Benchmarks-and-Specs.554426.0.html"&gt;Snapdragon 7c Gen
       +2&lt;/a&gt;)
       +and disk (eMMC, really?)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;My experience is similar to Tavis, at around 300ms of startup time I don't care
       +too much, but around 500ms+ is where I start to notice. I never had any issues
       +with startup time in NixOS itself (I had issues with macOS before, but it was
       +not actually the fault of macOS), but in the Chromebook it was awful: 600ms+
       +with &lt;a href="https://www.instabug.com/blog/understanding-cold-hot-and-warm-app-launch-time"&gt;hot
       +start&lt;/a&gt;,
       +while cold start it could take multiple seconds.&lt;/p&gt;
       +&lt;p&gt;We can check how long ZSH takes to start by using:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; zsh -ci &lt;span class="nb"&gt;exit&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;zsh -ic exit  0.04s user 0.10s system 100% cpu 0.143 total
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;-i&lt;/code&gt; flag here is important, because we are interested in the interactive
       +use of ZSH. Without this flag ZSH will ignore your &lt;code&gt;~/.zshrc&lt;/code&gt; file, and the
       +results will be meaningless.&lt;/p&gt;
       +&lt;p&gt;To do a more interesting benchmark, we can use
       +&lt;a href="https://github.com/sharkdp/hyperfine"&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;zsh -ic exit&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: zsh -ic exit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):     145.4 ms ±   4.2 ms    [User: 49.8 ms, System: 97.3 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):   138.6 ms … 155.3 ms    19 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hyperfine will run the command multiple times and take care of things like
       +shell startup time. A really great tool to have in your toolbox by the way, but
       +I digress.&lt;/p&gt;
       +&lt;p&gt;So let's do a little time travelling. Going back to commit
       +&lt;a href="https://github.com/thiagokokada/nix-configs/tree/b12757f90889653e359a1ab0a8cfd2f90cfabf31"&gt;&lt;code&gt;b12757f&lt;/code&gt;&lt;/a&gt;
       +from &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;nix-configs&lt;/a&gt;. Running
       +&lt;code&gt;hyperfine&lt;/code&gt; like above from my NixOS laptop, we have:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;zsh -ic exit&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: zsh -ic exit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):     218.6 ms ±   5.1 ms    [User: 70.6 ms, System: 151.5 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):   210.3 ms … 227.0 ms    13 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This doesn't look that bad, but let's see the same commit in my Chromebook:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;zsh -ic exit&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: zsh -ic exit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):     679.7 ms ±  40.2 ms    [User: 230.8 ms, System: 448.5 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):   607.3 ms … 737.0 ms    10 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yikes, this is much worse. And those are the results after I retried the
       +benchmark (so it is a hot start). The cold start times were above 3s. So let's
       +investigate what is happening here. We can profile what is taking time during
       +the startup of ZSH using &lt;a href="https://www.bigbinary.com/blog/zsh-profiling"&gt;zprof&lt;/a&gt;.
       +You can add the following in your &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# At the top of your ~/.zshrc file&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zmodload zsh/zprof
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# At the end of your ~/.zshrc file&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zprof
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or if using Home-Manager, use the
       +&lt;a href="https://nix-community.github.io/home-manager/options.xhtml#opt-programs.zsh.zprof.enable"&gt;&lt;code&gt;programs.zsh.zprof.enable&lt;/code&gt;&lt;/a&gt;
       +option. Once we restart ZSH, we will have something like:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;num  calls                time                       self            name
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-----------------------------------------------------------------------------------
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 1)    1          36.91    36.91   34.29%     30.47    30.47   28.31%  (anon) [/home/thiagoko/.zsh/plugins/zim-completion/init.zsh:13]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 2)    1          25.43    25.43   23.63%     25.43    25.43   23.63%  (anon) [/home/thiagoko/.zsh/plugins/zim-ssh/init.zsh:6]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 3)    1          22.00    22.00   20.45%     21.92    21.92   20.36%  _zsh_highlight_load_highlighters
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 4)    1          12.32    12.32   11.45%     12.32    12.32   11.45%  autopair-init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 5)    1           6.44     6.44    5.98%      6.44     6.44    5.98%  compinit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 6)    1           3.56     3.56    3.31%      3.48     3.48    3.23%  prompt_pure_state_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 7)    2           3.79     1.89    3.52%      2.85     1.43    2.65%  async
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 8)    1           0.93     0.93    0.87%      0.93     0.93    0.87%  async_init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 9)    6           0.93     0.15    0.86%      0.93     0.15    0.86%  is-at-least
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;10)    6           0.67     0.11    0.63%      0.67     0.11    0.63%  add-zle-hook-widget
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;11)    1           8.25     8.25    7.66%      0.61     0.61    0.57%  prompt_pure_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;12)    1           0.40     0.40    0.37%      0.40     0.40    0.37%  (anon) [/nix/store/p1zqypy7600fvfyl1v571bljx2l8zhay-zsh-autosuggestions-0.7.0/share/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;13)    5           0.31     0.06    0.29%      0.31     0.06    0.29%  add-zsh-hook
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;14)    1           0.60     0.60    0.56%      0.29     0.29    0.27%  (anon) [/home/thiagoko/.zsh/plugins/zim-input/init.zsh:5]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;15)    1           0.21     0.21    0.20%      0.21     0.21    0.20%  compdef
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;16)    1           0.10     0.10    0.09%      0.10     0.10    0.09%  _zsh_highlight__function_is_autoload_stub_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;17)    1           0.26     0.26    0.24%      0.08     0.08    0.08%  _zsh_highlight__function_callable_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;18)    1           0.08     0.08    0.08%      0.08     0.08    0.08%  prompt_pure_is_inside_container
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;19)    1           0.07     0.07    0.07%      0.07     0.07    0.07%  _zsh_highlight__is_function_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;20)    1           0.01     0.01    0.01%      0.01     0.01    0.01%  __wezterm_install_bash_prexec
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;21)    1           0.00     0.00    0.00%      0.00     0.00    0.00%  _zsh_highlight_bind_widgets
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt; ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I ommited some output for brevit. The first 2 things that shows are from the
       +&lt;a href="https://github.com/zimfw/zimfw"&gt;zimfw&lt;/a&gt;, the framework that I use to configure
       +my ZSH (similar to Oh-My-Zsh). I actually don't use &lt;code&gt;zimfw&lt;/code&gt; directly, instead I
       +just load some modules that I find useful, like the &lt;code&gt;zim-completion&lt;/code&gt; and
       +&lt;code&gt;zim-ssh&lt;/code&gt; that we can see above. By the way, Zim is generally really well
       +optimised for startup time, but those 2 modules are kind slow.&lt;/p&gt;
       +&lt;p&gt;For &lt;a href="https://github.com/zimfw/completion"&gt;&lt;code&gt;zim-completion&lt;/code&gt;&lt;/a&gt;, after taking a
       +look at it, there isn't much I could do. It seems that the reason
       +&lt;code&gt;zim-completion&lt;/code&gt; takes so long during startup is because it is trying to decide
       +if it needs to recompile the completions (and replacing it with just a naive
       +&lt;code&gt;autoload -U compinit &amp;amp;&amp;amp; compinit&lt;/code&gt; is even worse for startup performance). I
       +may eventually replace it for something else, but I really like what Zim brings
       +here, so I decided to not touch it for now.&lt;/p&gt;
       +&lt;p&gt;However &lt;a href="https://github.com/zimfw/ssh"&gt;&lt;code&gt;zim-ssh&lt;/code&gt;&lt;/a&gt; is another history. The only
       +reason I used it is to start a &lt;code&gt;ssh-agent&lt;/code&gt; and keep it between multiple ZSH
       +sessions. It shouldn't have that much influence in startup time. So I took a
       +look the code (since it is small, I am reproducing it here):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set up ssh-agent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Don&amp;#39;t do anything unless we can actually use ssh-agent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;+commands[ssh-agent]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ssh-add -l &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; ? &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Unable to contact the authentication agent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Load stored agent connection info&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; -r &lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/.ssh-agent
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -r &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    ssh-add -l &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; ? &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Start agent and store agent connection info&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;umask&lt;/span&gt; 066&lt;span class="p"&gt;;&lt;/span&gt; ssh-agent &amp;gt;! &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Load identities&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ssh-add -l &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; ? &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; -a zssh_ids
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    zstyle -a &lt;span class="s1"&gt;&amp;#39;:zim:ssh&amp;#39;&lt;/span&gt; ids &lt;span class="s1"&gt;&amp;#39;zssh_ids&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;zssh_ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      ssh-add &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/.ssh/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;^zssh_ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 2&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;else&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      ssh-add 2&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, this is bad. Let's assume the common path, where the &lt;code&gt;ssh-agent&lt;/code&gt; is
       +already running but you open a new shell instance (that doesn't have the
       +connection info yet so it will need to load). This will run &lt;code&gt;ssh-add&lt;/code&gt; at 4
       +times. How long does &lt;code&gt;ssh-add&lt;/code&gt; takes to run?&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine -Ni &lt;span class="s2"&gt;&amp;#34;ssh-add -l&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: ssh-add -l
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):       4.6 ms ±   1.1 ms    [User: 2.0 ms, System: 2.0 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):     3.4 ms …   8.7 ms    619 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="go"&gt;  Warning: Ignoring non-zero exit code.
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For those curious, &lt;code&gt;-N&lt;/code&gt; disables the Shell usage, that works better when the
       +command being tested is too fast.&lt;/p&gt;
       +&lt;p&gt;In average we have 4x4ms=16ms of startup time. But keep in mind the worst case
       +can be much worse. The question is, how can we improve the situation here?&lt;/p&gt;
       +&lt;p&gt;After taking a look, I decided to write my own code, based in some ideas stolen
       +from &lt;a href="https://github.com/ohmyzsh/ohmyzsh/blob/67581c53c6458566e174620361e84b364b9034d2/plugins/ssh-agent/ssh-agent.plugin.zsh"&gt;Oh-My-Zsh ssh-agent
       +plugin&lt;/a&gt;.
       +Here is final version of my
       +&lt;a href="https://github.com/thiagokokada/nix-configs/blob/e45a888f2bf3ce5644c3966f0b6371414d0291e2/home-manager/cli/ssh/ssh-agent.zsh"&gt;code&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zmodload zsh/net/socket
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_check_agent&lt;span class="o"&gt;(){&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -S &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zsocket &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_start_agent&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Test if $SSH_AUTH_SOCK is visible, in case we start e.g.: ssh-agent via&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# systemd service&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; _check_agent&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Get the filename to store/lookup the environment from&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;local&lt;/span&gt; -r &lt;span class="nv"&gt;ssh_env_cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.ssh-agent&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Check if ssh-agent is already running&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; /dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Test if $SSH_AUTH_SOCK is visible, e.g.: the ssh-agent is still alive&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; _check_agent&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# start ssh-agent and setup environment&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;umask&lt;/span&gt; &lt;span class="m"&gt;066&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    ssh-agent -s &amp;gt;! &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; /dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_start_agent
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unfunction _check_agent _start_agent
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea here is simple: using
       +&lt;a href="https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fnet_002fsocket-Module"&gt;&lt;code&gt;zsocket&lt;/code&gt;&lt;/a&gt;
       +module from ZSH itself to check if the &lt;code&gt;ssh-agent&lt;/code&gt; is working instead of
       +executing &lt;code&gt;ssh-add -l&lt;/code&gt;. The only case we run any program now is to start the
       +agent itself if needed. Let's run &lt;code&gt;hyperfine&lt;/code&gt; again:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     188.3 ms ±   8.2 ms    [User: 61.1 ms, System: 130.0 ms]
       +  Range (min … max):   170.9 ms … 198.4 ms    16 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Got a good improvement here already. Let's see &lt;code&gt;zprof&lt;/code&gt; again:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;num  calls                time                       self            name
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-----------------------------------------------------------------------------------
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 1)    1          41.23    41.23   48.66%     33.52    33.52   39.56%  (anon) [/home/thiagoko/.zsh/plugins/zim-completion/init.zsh:13]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 2)    1          22.23    22.23   26.24%     22.12    22.12   26.10%  _zsh_highlight_load_highlighters
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 3)    1           8.90     8.90   10.51%      8.90     8.90   10.51%  Gautopair-init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 4)    1           7.71     7.71    9.10%      7.71     7.71    9.10%  compinit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 5)    1           5.74     5.74    6.77%      5.60     5.60    6.60%  prompt_pure_state_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 6)    6           1.19     0.20    1.41%      1.19     0.20    1.41%  add-zle-hook-widget
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 7)    2           1.97     0.99    2.33%      1.14     0.57    1.34%  async
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 8)    6           0.87     0.15    1.03%      0.87     0.15    1.03%  is-at-least
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 9)    1           0.84     0.84    0.99%      0.84     0.84    0.99%  async_init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;10)    1           9.30     9.30   10.97%      0.72     0.72    0.84%  prompt_pure_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;11)    5           0.63     0.13    0.75%      0.63     0.13    0.75%  add-zsh-hook
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;12)    1           0.41     0.41    0.48%      0.41     0.41    0.48%  _start_agent
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;13)    1           0.31     0.31    0.37%      0.31     0.31    0.37%  (anon) [/nix/store/p1zqypy7600fvfyl1v571bljx2l8zhay-zsh-autosuggestions-0.7.0/share/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;14)    1           0.55     0.55    0.64%      0.24     0.24    0.28%  (anon) [/home/thiagoko/.zsh/plugins/zim-input/init.zsh:5]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;15)    1           0.14     0.14    0.16%      0.14     0.14    0.16%  prompt_pure_is_inside_container
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;16)    1           0.14     0.14    0.16%      0.14     0.14    0.16%  compdef
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;17)    1           0.09     0.09    0.11%      0.09     0.09    0.11%  _zsh_highlight__function_is_autoload_stub_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;18)    1           0.25     0.25    0.29%      0.08     0.08    0.09%  _zsh_highlight__function_callable_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;19)    1           0.07     0.07    0.09%      0.07     0.07    0.09%  _zsh_highlight__is_function_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;20)    1           0.01     0.01    0.01%      0.01     0.01    0.01%  __wezterm_install_bash_prexec
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;21)    1           0.01     0.01    0.01%      0.01     0.01    0.01%  _zsh_highlight_bind_widgets
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt; ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, there is nothing interesting here anymore. I mean, &lt;code&gt;zim-completion&lt;/code&gt; is
       +still the main culprit, but nothing to do for now. Instead of looking at
       +&lt;code&gt;zproof&lt;/code&gt;, let's take a look at my &lt;code&gt;~/.zshrc&lt;/code&gt; instead:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$options&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;zle&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; on &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/nix/store/sk6wsgp4h477baxypksz9rl8ldwwh9yg-fzf-0.54.0/bin/fzf --zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/nix/store/x3yblr73r5x76dmaanjk3333mvzxc49r-any-nix-shell-1.2.1/bin/any-nix-shell zsh &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; /dev/stdin
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/nix/store/330d6k81flfs6w46b44afmncxk57qggv-zoxide-0.9.4/bin/zoxide init zsh &lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/nix/store/8l9j9kdv9m0z0s30lp4yvrc9s5bcbgmx-direnv-2.34.0/bin/direnv hook zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So you see, starting all those programs during ZSH startup can hurt the shell
       +startup considerable. Not necessary for commands fast like &lt;code&gt;fzf&lt;/code&gt; (that is
       +written in Go), but let's see
       +&lt;a href="https://github.com/haslersn/any-nix-shell"&gt;&lt;code&gt;any-nix-shell&lt;/code&gt;&lt;/a&gt;, that is written
       +in shell script:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;any-nix-shell zsh&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: any-nix-shell zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):      16.0 ms ±   1.8 ms    [User: 5.6 ms, System: 10.5 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):    11.3 ms …  20.3 ms    143 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is bad, consistently bad actually. Even for commands that are fast, keep
       +in mind that there is a difference between the cold and hot start again. For
       +example, &lt;code&gt;fzf&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine -N &lt;span class="s2"&gt;&amp;#34;fzf --zsh&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: fzf --zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):       2.9 ms ±   0.9 ms    [User: 0.6 ms, System: 2.3 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):     1.7 ms …   6.8 ms    1113 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;See the range? While 1.7ms is something that is probably difficult to notice,
       +6.8ms can be noticiable, especially if this accumulates with other slow
       +starting apps.&lt;/p&gt;
       +&lt;p&gt;And the thing is, all those commands are just generating in the end a fixed
       +output, at least for the current version of the program. Can we pre-generate
       +them instead? If using Nix, of course we can:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# You need to disable the default integration&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enableZshIntegration&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fzf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enableZshIntegration&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zoxide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enableZshIntegration&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zsh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initExtra&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# bash&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # any-nix-shell
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;any-nix-shell-zsh&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getExe&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;any-nix-shell&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; zsh &amp;gt; $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # fzf
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fzf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/share/fzf/completion.zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fzf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/share/fzf/key-bindings.zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # zoxide
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zoxide-init-zsh&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getExe&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zoxide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; init zsh &amp;gt; $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # direnv
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;direnv-hook-zsh&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getExe&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; hook zsh &amp;gt; $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we can use &lt;code&gt;pkgs.runCommand&lt;/code&gt; to run those commands during build time and
       +&lt;code&gt;source&lt;/code&gt; the result. &lt;code&gt;fzf&lt;/code&gt; actually doesn't need this since we have the files
       +already generated in the package. I think this is one of those things that
       +really shows the power of Nix: I wouldn't do something similar if I didn't use
       +Nix because the risk of breaking something later is big (e.g.: forgetting to
       +update the generated files), but Nix makes those things trivial.&lt;/p&gt;
       +&lt;p&gt;Let's run &lt;code&gt;hyperfine&lt;/code&gt; again:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     162.3 ms ±   4.9 ms    [User: 52.7 ms, System: 111.1 ms]
       +  Range (min … max):   153.0 ms … 173.4 ms    19 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Another good improvement. The last change I did is switching between
       +&lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting"&gt;&lt;code&gt;zsh-syntax-highlighting&lt;/code&gt;&lt;/a&gt;
       +to
       +&lt;a href="https://github.com/zdharma-continuum/fast-syntax-highlighting"&gt;&lt;code&gt;zsh-fast-syntax-highlighting&lt;/code&gt;&lt;/a&gt;,
       +that is supposed to be faster and have better highlighting too. I got that from
       +&lt;code&gt;_zsh_highlight_load_highlighters&lt;/code&gt; using 26% of the time from my &lt;code&gt;zprof&lt;/code&gt; above.
       +And for the final &lt;code&gt;hyperfine&lt;/code&gt; in my laptop:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     138.3 ms ±   7.1 ms    [User: 47.5 ms, System: 91.9 ms]
       +  Range (min … max):   123.8 ms … 157.9 ms    21 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;A ~36% improvement, not bad. Let's see how it fares in my Chromebook:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     278.2 ms ±  46.9 ms    [User: 88.0 ms, System: 184.8 ms]
       +  Range (min … max):   204.7 ms … 368.5 ms    11 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;An even more impressive ~59% improvement. And yes, the shell startup now feels
       +much better.&lt;/p&gt;</summary></entry><entry><title>Quick bits: realise Nix symlinks</title><link href="https://kokada.dev/blog/quick-bits-realise-nix-symlinks/" rel="alternate"/><published>2024-08-01T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-realise-nix-symlinks/</id><summary type="html">&lt;p&gt;When you are using Nix, especially with
       +&lt;a href="https://github.com/nix-community/home-manager/"&gt;Home-Manager&lt;/a&gt;, there are times
       +when you want to test something or maybe debug some issue in your
       +configuration. Those times it would be really convenient if you could avoid a
       +rebuild of your Home-Manager configuration, since this takes time until
       +evaluation and activation.&lt;/p&gt;
       +&lt;p&gt;For those times I have this small script in my Nix configuration called
       +&lt;code&gt;realise-symlinks&lt;/code&gt;, that is defined as:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;realise-symlink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeShellApplication&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;realise-symlink&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;runtimeInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;coreutils&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      for file in &amp;#34;$@&amp;#34;; do
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        if [[ -L &amp;#34;$file&amp;#34; ]]; then
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          if [[ -d &amp;#34;$file&amp;#34; ]]; then
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            tmpdir=&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;&amp;#39;&amp;#39;$&lt;/span&gt;&lt;span class="s1"&gt;{file}.tmp&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mkdir -p &amp;#34;$tmpdir&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            cp --verbose --recursive &amp;#34;$file&amp;#34;/* &amp;#34;$tmpdir&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            unlink &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mv &amp;#34;$tmpdir&amp;#34; &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            chmod --changes --recursive +w &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          else
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            cp --verbose --remove-destination &amp;#34;$(readlink &amp;#34;$file&amp;#34;)&amp;#34; &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            chmod --changes +w &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          fi
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        else
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          &amp;gt;&amp;amp;2 echo &amp;#34;Not a symlink: $file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          exit 1
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        fi
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      done
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;realise-symlink&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea of this script is that you can call it against a symlink against Nix
       +store and it will realise, e.g.: convert to an &amp;quot;actual&amp;quot; file, e.g.:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 thiagoko users 69 Aug  1 00:10 .zshrc -&amp;gt; /nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; realise-symlink .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;removed &amp;#39;.zshrc&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;/nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zshrc&amp;#39; -&amp;gt; &amp;#39;.zshrc&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;.zshrc&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 5.8K Aug  1 00:16 .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It also add write permissions to the resulting file, to make it easier to edit.
       +By the way, it also works with directories:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 thiagoko users 90 Aug  1 00:10 zim-completion -&amp;gt; /nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zsh/plugins/zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; realise-symlink zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/init.zsh&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/init.zsh&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/init.zsh.zwc&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/init.zsh.zwc&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/LICENSE&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/LICENSE&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/README.md&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/README.md&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/init.zsh&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/init.zsh.zwc&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/LICENSE&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/README.md&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;total 28K
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;drwxr-xr-x 1 thiagoko users   72 Aug  1 00:18 .
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;drwxr-xr-x 1 thiagoko users  130 Aug  1 00:18 ..
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 5.3K Aug  1 00:18 init.zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users  12K Aug  1 00:18 init.zsh.zwc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 1.3K Aug  1 00:18 LICENSE
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 2.2K Aug  1 00:18 README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After you finish whatever you are testing, to return to your configuration you
       +can just delete those files and re-run your Home-Manager activation:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rm -rf .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemctl restart home-manager-&amp;lt;user&amp;gt;.service &lt;span class="c1"&gt;# or `home-manager switch`&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 thiagoko users 69 Aug  1 00:20 .zshrc -&amp;gt; /nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It even works with system files (i.e.: NixOS):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo realise-symlink /etc/nix/nix.conf
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[sudo] password for thiagoko:
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;removed &amp;#39;nix.conf&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;/etc/static/nix/nix.conf&amp;#39; -&amp;gt; &amp;#39;nix.conf&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;nix.conf&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But I never needed for this case since it is more rare to me to experiment with
       +OS level configuration.&lt;/p&gt;</summary></entry><entry><title>Generating YAML files with Nix</title><link href="https://kokada.dev/blog/generating-yaml-files-with-nix/" rel="alternate"/><published>2024-07-31T00:00:00+00:00</published><id>https://kokada.dev/blog/generating-yaml-files-with-nix/</id><summary type="html">&lt;p&gt;I hate YAML. Instead of writing an essay on why I hate YAML, I can just link to
       +&lt;a href="https://noyaml.com/"&gt;noyaml.com&lt;/a&gt;. In my personal projects I will never use it,
       +preferring either JSON, &lt;a href="https://toml.io/en/"&gt;TOML&lt;/a&gt; or even plain old
       +&lt;a href="https://en.wikipedia.org/wiki/INI_file"&gt;INI&lt;/a&gt; files depending on the use case.
       +However the ship has sailed already, there are tons of projects everywhere that
       +uses YAML: from most CI systems (&lt;a href="https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions"&gt;GitHub
       +Actions&lt;/a&gt;,
       +&lt;a href="https://circleci.com/docs/introduction-to-yaml-configurations/"&gt;CircleCI&lt;/a&gt;,
       +&lt;a href="https://docs.travis-ci.com/user/build-config-yaml"&gt;Travis&lt;/a&gt;, &lt;em&gt;et tu&lt;/em&gt;
       +&lt;a href="https://man.sr.ht/builds.sr.ht/"&gt;builds.sr.ht&lt;/a&gt;), to
       +&lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/"&gt;Kubernetes&lt;/a&gt;,
       +or in almost every
       +&lt;a href="https://guides.rubyonrails.org/configuring.html#configuring-a-database"&gt;Rails&lt;/a&gt;
       +application.&lt;/p&gt;
       +&lt;p&gt;One way to avoid at least some issues with the language is to write YAML in
       +another language. I will show my solution in one of my &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;personal
       +repositories&lt;/a&gt;, writing Nix to
       +generate GitHub Actions configuration files. Bonus points for validating the
       +result against the schema of GitHub Actions, so the famous &amp;quot;this is supposed to
       +be string instead of a list of strings&amp;quot; is gone.&lt;/p&gt;
       +&lt;p&gt;Let's start with the basics: YAML is supposed to be a &lt;a href="https://stackoverflow.com/a/1729545"&gt;superset of
       +JSON&lt;/a&gt;. What that means is that a JSON file
       +&lt;a href="https://yaml.org/spec/1.2-old/spec.html#id2759572"&gt;can be parsed&lt;/a&gt; by a YAML
       +parser. And Nix itself generates JSON natively, after all, Nix can be imagined
       +as &lt;a href="https://nix.dev/tutorials/nix-language.html"&gt;&amp;quot;JSON with functions&amp;quot;&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;To make things easier, I will assume that you have the &lt;code&gt;nix-commands&lt;/code&gt; and
       +&lt;code&gt;flakes&lt;/code&gt; enabled as &lt;code&gt;experimental-features&lt;/code&gt; in your Nix configuration. If not,
       +go &lt;a href="https://wiki.nixos.org/wiki/Flakes"&gt;here&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;Using the &lt;code&gt;nix eval&lt;/code&gt; command, we can generate a JSON expression from Nix by:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix &lt;span class="nb"&gt;eval&lt;/span&gt; --expr &lt;span class="s1"&gt;&amp;#39;{ foo = &amp;#34;bar&amp;#34;; }&amp;#39;&lt;/span&gt; --json
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;{&amp;#34;foo&amp;#34;:&amp;#34;bar&amp;#34;}
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, typing long excerpts of Nix code inside the console would be
       +impractical. We can write the following code inside a &lt;code&gt;foo.nix&lt;/code&gt; file instead:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix &lt;span class="nb"&gt;eval&lt;/span&gt; --file foo.nix --json
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;{&amp;#34;foo&amp;#34;:&amp;#34;bar&amp;#34;}
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While you can use a JSON output as an input for YAML parsers, it is probably
       +not the &lt;a href="https://metacpan.org/pod/JSON::XS#JSON-and-YAML"&gt;best idea&lt;/a&gt;. Sadly (or
       +maybe not), Nix has no native functionality to export data to YAML. However,
       +since we are using Nix, it is trivial to use &lt;code&gt;nixpkgs&lt;/code&gt; to use some program to
       +convert from JSON to YAML.&lt;/p&gt;
       +&lt;p&gt;To start, let's create a new directory, move our &lt;code&gt;foo.nix&lt;/code&gt; file to it, create a
       +new &lt;code&gt;flake.nix&lt;/code&gt; file and put the following contents:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Generate YAML files with Nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:NixOS/nixpkgs/nixos-unstable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x86_64-linux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;toYAML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;toYAML&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;yj&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;./go.nix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;passAsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;json&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;# will be available as `$jsonPath`&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mkdir -p $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            yj -jy &amp;lt; &amp;#34;$jsonPath&amp;#34; &amp;gt; $out/go.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are loading the &lt;code&gt;./foo.nix&lt;/code&gt; as a Nix file, converting it to JSON with
       +&lt;code&gt;builtins.toJSON&lt;/code&gt; function, and finally, using &lt;code&gt;pkgs.runCommand&lt;/code&gt; and its
       +&lt;code&gt;passAsFile&lt;/code&gt; option to load the contents of the JSON file into
       +&lt;a href="https://github.com/sclevine/yj"&gt;yj&lt;/a&gt;, that converts between serialisation
       +formats (&lt;code&gt;-jy&lt;/code&gt; flag means &amp;quot;JSON to YAML&amp;quot;). The reason I choose &lt;code&gt;yj&lt;/code&gt; is mostly
       +because it is a single binary Go program, but you can use whatever you prefer.&lt;/p&gt;
       +&lt;p&gt;By the way, there is a
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/9f918d616c5321ad374ae6cb5ea89c9e04bf3e58/lib/generators.nix#L805"&gt;&lt;code&gt;lib.generators.toYAML&lt;/code&gt;&lt;/a&gt;
       +inside &lt;code&gt;nixpkgs.lib&lt;/code&gt;, but as of the day of this post it only calls
       +&lt;code&gt;lib.strings.toJSON&lt;/code&gt; (that in turn, calls &lt;code&gt;builtins.toJSON&lt;/code&gt;). So it doesn't
       +really help here. Another option would be &lt;code&gt;pkgs.formats.yaml.generate&lt;/code&gt;, that
       +converts between formats, but it calls
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/008ceae1a2b47a84d7aa01e55f8468272c70b9ee/pkgs/pkgs-lib/formats.nix#L77-L84"&gt;&lt;code&gt;remarshal&lt;/code&gt;&lt;/a&gt;
       +(in Python), so not my favorite choice.&lt;/p&gt;
       +&lt;p&gt;If we run the following commands, we can see the result:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#packages.x86_64-linux.toYAML
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cat result/foo.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo: bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That is the basic idea. To have a more realistic example, let's convert the
       +&lt;a href="https://github.com/thiagokokada/blog/blob/4e3f25485c6682f3e066b219df2290934bc0d256/.github/workflows/go.yml"&gt;&lt;code&gt;go.yml&lt;/code&gt;&lt;/a&gt;,
       +that builds this blog, to Nix:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;branches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;main&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;runs-on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ubuntu-latest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;actions/checkout@v4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Set up Go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;actions/checkout@v4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;with&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;go-version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.21&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;make&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Publish&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;make publish&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MATAROA_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;&amp;#39;&amp;#39;$&lt;/span&gt;&lt;span class="s1"&gt;{{ secrets.MATAROA_TOKEN }}&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Commit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stefanzweifel/git-auto-commit-action@v5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;with&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;README/rss:update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some interesting things to highlight: &lt;code&gt;with&lt;/code&gt; is a reserved word in Nix, so we
       +need to quote it. Not a problem, but something to be aware. And the template
       +string in GitHub Actions uses the same &lt;code&gt;${}&lt;/code&gt; that Nix uses, so we need to
       +escape.&lt;/p&gt;
       +&lt;p&gt;And after running the following commands:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ nix build .#packages.x86_64-linux.toYAML
       +$ cat result/go.yaml
       +jobs:
       +  build:
       +    permissions:
       +      contents: write
       +    runs-on: ubuntu-latest
       +    steps:
       +      - uses: actions/checkout@v4
       +      - name: Set up Go
       +        uses: actions/checkout@v4
       +        with:
       +          go-version: &amp;quot;1.21&amp;quot;
       +      - name: Update
       +        run: make
       +      - env:
       +          MATAROA_TOKEN: ${{ secrets.MATAROA_TOKEN }}
       +        name: Publish
       +        run: make publish
       +      - name: Commit
       +        uses: stefanzweifel/git-auto-commit-action@v5
       +        with:
       +          commit_message: README/rss:update
       +name: Go
       +&amp;quot;on&amp;quot;:
       +  push:
       +    branches:
       +      - main
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Yes, the keys are not in the same order as we defined, since Nix, like most
       +programming languages (with the exception of
       +&lt;a href="https://mail.python.org/pipermail/python-dev/2017-December/151283.html"&gt;Python&lt;/a&gt;),
       +do not guarantee the insertion order in maps/dicts/attrsets/whatever. But I
       +really hope whatever is consuming your YAML is not relying in the order the
       +keys are defined (this would be more cursed than YAML already is).&lt;/p&gt;
       +&lt;p&gt;So that is basically it. For the bonus points that I talked at the start of the
       +post, we can modify &lt;code&gt;pkgs.runCommand&lt;/code&gt; to run some kind of validator. I use
       +&lt;a href="https://github.com/mpalmer/action-validator"&gt;&lt;code&gt;action-validator&lt;/code&gt;&lt;/a&gt;, one that I
       +particularly packaged in
       +&lt;a href="https://github.com/NixOS/nixpkgs/pull/260217"&gt;nixpkgs&lt;/a&gt; to use in those cases.
       +But you could use e.g.: a validator of Kubernetes YAML. Or a generic YAML lint
       +like this &lt;a href="https://github.com/adrienverge/yamllint"&gt;one&lt;/a&gt;. The possibilities are
       +endless.&lt;/p&gt;
       +&lt;p&gt;Let's modify our &lt;code&gt;flake.nix&lt;/code&gt; to add the validation:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x86_64-linux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;toYAML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;toYAML&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;action-validator&lt;/span&gt; &lt;span class="n"&gt;yj&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;./go.nix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;passAsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;json&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mkdir -p $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            yj -jy &amp;lt; &amp;#34;$jsonPath&amp;#34; &amp;gt; $out/go.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            action-validator -v $out/go.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And let's add an error in our &lt;code&gt;go.nix&lt;/code&gt; file:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/go.nix b/go.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 25e0596..8c00033 100644
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;&lt;span class="gd"&gt;--- a/go.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;&lt;/span&gt;&lt;span class="gi"&gt;+++ b/go.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -5,7 +5,7 @@
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;&lt;/span&gt;   jobs = {
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     build = {
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       runs-on = &amp;#34;ubuntu-latest&amp;#34;;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-      permissions.contents = &amp;#34;write&amp;#34;;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;&lt;/span&gt;&lt;span class="gi"&gt;+      permissions.contents = [ &amp;#34;write&amp;#34; ];
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;&lt;/span&gt;       steps = [
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         { uses = &amp;#34;actions/checkout@v4&amp;#34;; }
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         {
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, let's try to build our YAML file again:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ nix build .#packages.x86_64-linux.toYAML
       +error: builder for '/nix/store/j8wr6j1pvyf986sf74hqw8k31lvlzac5-toYAML.drv' failed with exit code 1;
       +       last 25 log lines:
       +       &amp;gt;                                 &amp;quot;Additional property 'runs-on' is not allowed&amp;quot;,
       +       &amp;gt;                             ),
       +       &amp;gt;                             path: &amp;quot;/jobs/build&amp;quot;,
       +       &amp;gt;                             title: &amp;quot;Property conditions are not met&amp;quot;,
       +       &amp;gt;                         },
       +       &amp;gt;                         Properties {
       +       &amp;gt;                             code: &amp;quot;properties&amp;quot;,
       +       &amp;gt;                             detail: Some(
       +       &amp;gt;                                 &amp;quot;Additional property 'steps' is not allowed&amp;quot;,
       +       &amp;gt;                             ),
       +       &amp;gt;                             path: &amp;quot;/jobs/build&amp;quot;,
       +       &amp;gt;                             title: &amp;quot;Property conditions are not met&amp;quot;,
       +       &amp;gt;                         },
       +       &amp;gt;                         Required {
       +       &amp;gt;                             code: &amp;quot;required&amp;quot;,
       +       &amp;gt;                             detail: None,
       +       &amp;gt;                             path: &amp;quot;/jobs/build/uses&amp;quot;,
       +       &amp;gt;                             title: &amp;quot;This property is required&amp;quot;,
       +       &amp;gt;                         },
       +       &amp;gt;                     ],
       +       &amp;gt;                 },
       +       &amp;gt;             ],
       +       &amp;gt;         },
       +       &amp;gt;     ],
       +       &amp;gt; }
       +       For full logs, run 'nix log /nix/store/j8wr6j1pvyf986sf74hqw8k31lvlzac5-toYAML.drv'.
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Yes, the output of &lt;code&gt;action-validator&lt;/code&gt; is awfully verbose, but it is still
       +better than making &lt;a href="https://x.com/eric_sink/status/1430954572848287744"&gt;&amp;quot;8 commits/push in one
       +hour&amp;quot;&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;If you are interested in how a more advantage usage of this technique is,
       +including usage of functions and constants to share common steps between
       +different actions, please take a look at the
       +&lt;a href="https://github.com/thiagokokada/nix-configs/tree/master/actions"&gt;actions&lt;/a&gt;
       +(&lt;a href="https://github.com/thiagokokada/nix-configs/tree/fbbc8f935cc7f774f691196e1612bbf7dd2054b5/actions"&gt;permalink&lt;/a&gt;)
       +in my &lt;code&gt;nix-config&lt;/code&gt; repository.&lt;/p&gt;</summary></entry><entry><title>First impressions: FPGBC</title><link href="https://kokada.dev/blog/first-impressions-fpgbc/" rel="alternate"/><published>2024-07-30T00:00:00+00:00</published><id>https://kokada.dev/blog/first-impressions-fpgbc/</id><summary type="html">&lt;p&gt;Here is something for nostalgia: I just put together a &lt;a href="https://en.wikipedia.org/wiki/Game_Boy_Color"&gt;Game Boy
       +Color&lt;/a&gt; made of completely new
       +parts for a friend: here is the
       +&lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;FPGBC&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;The &lt;em&gt;FP&lt;/em&gt; part of the name comes from
       +&lt;a href="https://en.wikipedia.org/wiki/Field-programmable_gate_array"&gt;FPGA&lt;/a&gt;, because
       +instead of software emulation this device use FPGA to reproduce the device.
       +While I am not convinced that FPGA is necessary more accurate than a good
       +software emulator, one advantage of FPGA is the (possible) lower input latency
       +thanks to the avoidance of complexity to handle the user input (e.g.: the
       +Operational System). A quick playthrough against &lt;a href="https://en.wikipedia.org/wiki/Motocross_Maniacs"&gt;Motocross
       +Maniacs&lt;/a&gt; seems to be fine, but
       +I can't see much difference from my &lt;a href="https://retrogamecorps.com/2022/05/15/miyoo-mini-v2-guide/"&gt;Miyoo
       +Mini+&lt;/a&gt; (I will do
       +more comparisons between the two devices later), that is a software emulation
       +device.&lt;/p&gt;
       +&lt;p&gt;But I think focusing in accuracy is wrong, the main reason of getting a device
       +like this one is for nostalgia, and this definitely hit the mark. The quality
       +of the case is as good as I remember the original, and most of the details are
       +replicate perfectly, including reproduction stickers in the back of the device.
       +The only differences that I can find is the usage of USB-C port for charging in
       +place of the barrel jack power adapter (thanks!), and the fact that the screen
       +bezels are smaller compared to the original (because the screen is bigger) and
       +doesn't include the Game Boy Color logo (that is fine in my opinion, since it
       +would look weird in the fine bezels). It even has a supposedly working &lt;a href="https://en.wikipedia.org/wiki/Game_Link_Cable"&gt;Link
       +Cable&lt;/a&gt; (I don't have another
       +Game Boy to test). Sadly it is missing the infrared sensor, but the usage of
       +that was pretty limited anyway.&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175245569.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175245569.jpg" alt="FPGBC running Tetris."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175131157.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175131157.jpg" alt="Back of FPGBC. It includes even reproduction stickers of the original."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;So how well does it work? I can't say for sure. I don't have any original games
       +with me, so I am relying in backups and a
       +&lt;a href="https://en.wikipedia.org/wiki/Flashcard"&gt;flashcard&lt;/a&gt; for now. Many games that I
       +tested works fine, a few of them have graphical issues that can be fixed in the
       +menu (more about it later), and some of them doesn't boot. But I don't know if
       +the issue with the games not booting are because of the roms, the flashcard or
       +the device itself.&lt;/p&gt;
       +&lt;p&gt;By the way, the flashcard I am using is a cheap knockoff of an &lt;a href="https://gbatemp.net/review/everdrive-gb.141/"&gt;Everdrive
       +GB&lt;/a&gt;. This FPGBC came with
       +firmware v1.09, while there is an update available for v1.10 in the
       +&lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;website&lt;/a&gt;. I had an weird issue in
       +the new firmware where no games would boot with this knockoff Everdrive so I
       +had to go back to v1.09, but again, I am not sure if the issue was fact that I
       +am using a knockoff device or this would happen with an original Everdrive GB.
       +If you are going to buy a proper Everdrive, you probably wouldn't get a
       +Everdrive GB anyway since it is discontinued, and it seems the &lt;a href="https://www.reddit.com/r/Gameboy/comments/1atwjh3/fpgbc_everdrive_compatibility/"&gt;newer
       +versions&lt;/a&gt;
       +have better compatibility with FPGBC.&lt;/p&gt;
       +&lt;p&gt;Sadly that the update didn't work, since there is this
       +&lt;a href="https://github.com/makhowastaken/GWGBC_FW"&gt;repository&lt;/a&gt; that patches the
       +firmware to boot the original logo instead of the ugly FPGBC one. And yes, for
       +some reason the v1.09 firmware from this repository still doesn't work with my
       +knockoff Everdrive.&lt;/p&gt;
       +&lt;p&gt;By the way, it seems the device is not easy to brick: I borked the firmware
       +update process once while trying to downgrade back to v1.09, resulting in a
       +black screen when I turned on the console. But just connecting the device to
       +the computer and powering on, I could flash the firmware again and the device
       +came back to life.&lt;/p&gt;
       +&lt;p&gt;About the features of the device: if you press the volume button (yes, you can
       +press it now), it opens the following menu:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_210604830.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_210604830.jpg" alt="FPGBC menu."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;The first 2 options are the LCD backlight (&lt;code&gt;BKLT&lt;/code&gt;) and volume (&lt;code&gt;VOL&lt;/code&gt;). I didn't talk about
       +those, but the LCD screen seems to be IPS, and the quality is really good, and
       +also looks bright enough to play even under bad lightining conditions. And the
       +speaker has good quality, the sound is better than I remember, but sadly the
       +maximum volume is kind low. Still should be enough for playing in a quiet room.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;DISPMOD&lt;/code&gt; is probably the most controversial option: it allow you to set which
       +scale you want. Anything with &lt;code&gt;EMU&lt;/code&gt; at the end means emulating the original
       +colors, and as far I remember it gets really close. You can also chose betwen
       +&lt;code&gt;X4&lt;/code&gt;, &lt;code&gt;X4P&lt;/code&gt; and &lt;code&gt;FUL&lt;/code&gt;, the last one is the one shown in the photos where the
       +image fills the whole screen at the cost of non-integer scaling. &lt;code&gt;X4&lt;/code&gt; is
       +integer scaling, however the image doesn't fill the whole screen. The &lt;code&gt;X4P&lt;/code&gt;
       +also includes a pixel effect that makes the image closer than the original
       +screen. It actually looks good, but the fact that I chose a white border for
       +this FPGBC makes the border really distracting. Maybe the black one is a better
       +choice if you want integer scale.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;CORE&lt;/code&gt; is simple: you can choose between &lt;code&gt;GB&lt;/code&gt; (Game Boy) or &lt;code&gt;GBC&lt;/code&gt; (Game Boy
       +Color). For those who don't know, you can run Game Boy games in Game Boy Color
       +and they will be automatically colorised. Some people don't like this and
       +prefer the colors of &lt;code&gt;GB&lt;/code&gt;, so you have this option. The &lt;code&gt;GB_PALETTE&lt;/code&gt; allows you
       +to chose the color in GB mode, for example, the green-ish colors from the
       +original Game Boy or the blue-ish colors from &lt;a href="https://nintendo.fandom.com/wiki/Game_Boy_Light"&gt;Game Boy
       +Light&lt;/a&gt;. And yes, you can
       +choose the color palette for Game Boy games running in &lt;code&gt;GBC&lt;/code&gt; mode by pressing a
       +&lt;a href="https://gbstudiocentral.com/tips/game-boy-color-modes/"&gt;button combination&lt;/a&gt; at
       +the boot screen, but it seems not working in my unit and again, not sure if the
       +fault is my knockoff Everdrive.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;FRAME_MIX&lt;/code&gt; basically is an option that makes some effects, like transparency
       +in &lt;a href="https://en.wikipedia.org/wiki/Wave_Race"&gt;Wave Race&lt;/a&gt;, to work at the cost of
       +introducing blurriness. The reason for this is that those effects depends in
       +the fact that the Game Boy screen was slow refresh, so you could rely on it by
       +rapidly changing pixels to create some interesting effects, but sadly those
       +effects doesn't work well in modern displays.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;GB_CLRFIX&lt;/code&gt; is the option I mentioned before, where some Game Boy games just
       +get completely wrong colors for some reason, e.g.: &lt;a href="https://en.wikipedia.org/wiki/The_Addams_Family_(video_game)"&gt;The Addams
       +Family&lt;/a&gt;. Turning
       +on fixes those games, but I am not sure if this option breaks other games.&lt;/p&gt;
       +&lt;p&gt;Finally, &lt;code&gt;SPD&lt;/code&gt; allows you to increase or decrease the CPU clock, slowing or
       +speeding up the games (including the sound). The result can be hilarious, so I
       +think this is a nice addition to the features. Sadly you can't know what the
       +default speed is, so you need to rely on sound to adjust back to the default.&lt;/p&gt;
       +&lt;p&gt;So in the end, can I recommend a FPGBC? I am not sure. If you want a device to
       +play games, I still think something like a Miyoo Mini+ is a better choice. Not
       +only you will have access to more games from different platforms, you also
       +don't need to rely on flashcards or cartridges. Also it has way more features
       +than FPGBC, like wireless multiplayer,
       +&lt;a href="https://retroachievements.org/"&gt;RetroArchivements&lt;/a&gt; and save states.&lt;/p&gt;
       +&lt;p&gt;But the actual reason to get a FPGBC is nostalgia, and for that I think the
       +FPGBC is difficult to beat. The price of the &lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;kit to
       +assemble&lt;/a&gt; ($69.90) is cheaper than
       +most Game Boy's in good condition you can find in eBay, and you get for that
       +price a rechargable battery, an amazing quality screen, the PCB and the
       +speaker. You need to buy separately the case and the buttons, but in total you
       +will still end up paying less, and allows you to fully customise your build.
       +And the result device is not only in mint condition, it is really convenient
       +too: recharging batteries (via USB-C even) is much more convenient than buying
       +AA batteries, and the screen not only is better but it even has backlight. You
       +can also buy a fully built console for
       +&lt;a href="https://funnyplaying.com/products/fpgbc-console"&gt;$99.00&lt;/a&gt;, but you have less
       +options of customisation.&lt;/p&gt;
       +&lt;p&gt;This is the classic case of do what I say, don't do what I do. This FPGBC is a
       +gift, and I will buy another one soon. Can't wait to play &lt;a href="https://en.wikipedia.org/wiki/Pok%C3%A9mon_Gold_and_Silver"&gt;Pokémon
       +Gold&lt;/a&gt; in (almost)
       +original hardware again.&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_123847458.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_123847458.jpg" alt="The kit before assemble."&gt;&lt;/a&gt;&lt;/p&gt;</summary></entry><entry><title>Quick bits: why you should automate everything</title><link href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything/" rel="alternate"/><published>2024-07-29T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-why-you-should-automate-everything/</id><summary type="html">&lt;p&gt;If everything works as expected this blog post should appear in &lt;a href="https://kokada.capivaras.dev/"&gt;in my
       +blog&lt;/a&gt; without I ever touching the
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; website. I rewrote my &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;previous Python
       +script&lt;/a&gt; to Go
       +(&lt;a href="https://github.com/thiagokokada/blog/blob/3c39e0f7cd58b1af885f69871490b05bf6fc7d99/blog.go"&gt;permalink&lt;/a&gt;)
       +since my attempt to generate proper description to the RSS feed resulted in
       +slow startup times (not because of Python, but because of my usage of
       +&lt;code&gt;nix-shell&lt;/code&gt; since I didn't want to deal with
       +&lt;a href="https://docs.python.org/3/library/venv.html"&gt;venv&lt;/a&gt; or anything to manage my
       +Python dependencies).&lt;/p&gt;
       +&lt;p&gt;My previous workflow of this blog already involved me writing the texts in
       +&lt;a href="https://neovim.io/"&gt;neovim&lt;/a&gt;, copying and pasting the result in the
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; website and publishing. This was not
       +that bad, except that it seems I have a heavy tendency of editing my posts
       +multiple times. Copying and pasting data between neovim and the website became
       +tedious, so I decided to give up and automate the whole process.&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt; (the blog platform
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; run) has a reasonable good
       +&lt;a href="https://mataroa.blog/api/docs/"&gt;API&lt;/a&gt;, and it only took a few hours to get a
       +version of publishing working (it would take less if
       +&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, the framework Mataroa is written, did
       +not have a weird behavior with URLs missing a trailing &lt;code&gt;/&lt;/code&gt;). An additional few
       +lines of
       +&lt;a href="https://github.com/thiagokokada/blog/blob/51b20612335c7f4312a51a0f436235b4b701ce8b/.github/workflows/go.yml"&gt;YAML&lt;/a&gt;
       +to make GitHub Actions trigger a pipeline and now I should never have to
       +manually update my blog again.&lt;/p&gt;
       +&lt;p&gt;I could have not done this. I mean, I probably wasted more time writing an
       +automation than I actually wasted publishing manually. But the manual process
       +is easy to miss, and I already did multiple mistakes publishing in the manual
       +method. For example, when writing the Markdown files, each post is written in a
       +particular format, where the first header is considered the title, so I need to
       +remove it from the contents during publication. But of course, this is easy to
       +miss, and I had to fix this multiple times already.&lt;/p&gt;
       +&lt;p&gt;So yes, I think this is a good lesson on why you should automate everything. It
       +is more than just about &lt;a href="https://xkcd.com/1205/"&gt;time savings&lt;/a&gt;, it is about
       +reducing mistakes and even documenting (even if poorly) a process. I mean, the
       +code I wrote is not that great, but I can definitely rely on it in the future
       +to remember what I need to do. It will be much faster than trying to learn from
       +scratch again.&lt;/p&gt;</summary></entry><entry><title>Go, a reasonable good language</title><link href="https://kokada.dev/blog/go-a-reasonable-good-language/" rel="alternate"/><published>2024-07-29T00:00:00+00:00</published><id>https://kokada.dev/blog/go-a-reasonable-good-language/</id><summary type="html">&lt;p&gt;Go was one of the languages that I always was interested to learn, but never
       +got the hang of it. I first got interested in the language when I was in my
       +first job, between 2016-2018. At the time the language was a completely
       +different beast: no modules, no generics, no easy way to error wrap yet, etc.&lt;/p&gt;
       +&lt;p&gt;Go forward 2023 (no pun indented), I wrote my &lt;a href="https://github.com/thiagokokada/twenty-twenty-twenty/"&gt;first project in
       +Go&lt;/a&gt;, wrote some scripts
       +at &lt;code&gt;$CURRENT_JOB&lt;/code&gt; in the language, and now wrote &lt;a href="https://github.com/thiagokokada/hyprland-go/"&gt;my first
       +library&lt;/a&gt; (with an &lt;a href="https://kokada.dev/blog/writing-nixos-tests-for-fun-and-profit"&gt;interesting
       +way to run
       +CI&lt;/a&gt;). I am also
       +writing more
       +&lt;a href="https://github.com/thiagokokada/nix-configs/blob/8c559527ed12e1d4f57a3fc5c72630b956f4c290/home-manager/desktop/wayland/hyprland/hyprtabs/hyprtabs.go"&gt;scripts&lt;/a&gt;
       +in the language, where I would prefer to use Bash or Python before. Heck, even
       +this blog is automatically published with a &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;Go
       +script&lt;/a&gt;,
       +that used to be a &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;Python
       +one&lt;/a&gt; before. I can
       +say that nowadays it is another language in my toolbox, and while it is still a
       +love and hate relationship, recently it is more about love and less about hate.&lt;/p&gt;
       +&lt;p&gt;The points that I love about Go is probably obvious for some, but still
       +interesting to talk about anyway. The fact that the language generates static
       +binaries by default and have fast compilation times is something that I
       +apreciate since I first heard about the language, and now that I am using the
       +language frequently, are points thatI appreciate even more. Something about
       +getting almost instant feedback after changing a line of code and running &lt;code&gt;go run&lt;/code&gt; (even with its quirks) are great for the developer experience. This is the
       +main reason why I am using the language more frequently for scripts.&lt;/p&gt;
       +&lt;p&gt;Then we have the fast startup times. I am kind of sensitive to latency,
       +especially of command line utilities that need to answer fast when I expect
       +them to be fast (e.g.: &lt;code&gt;foo --help&lt;/code&gt;). This is one part where I could have
       +issues in Python, especially for more complex programs, but in Go it is rarely
       +an issue.&lt;/p&gt;
       +&lt;p&gt;Modules are also fantastic. It is not without its weirdness (like everything in
       +Go ecossystem), but the fact that it is so easy to add and manage dependencies
       +in a project using only the &lt;code&gt;go&lt;/code&gt; CLI is great. I also like that it generates a
       +hash of every dependency, making it reproducible (well, probably not at Nix
       +level, but still reproducible).&lt;/p&gt;
       +&lt;p&gt;Since I started to talk about &lt;code&gt;go&lt;/code&gt; CLI, what a great tool! The fact that you
       +can manage dependencies, generate documentation, format code, lint, run
       +tests/&lt;a href="https://blog.logrocket.com/benchmarking-golang-improve-function-performance/"&gt;benchmarks&lt;/a&gt;/&lt;a href="https://go.dev/doc/security/fuzz/"&gt;fuzzing&lt;/a&gt;,
       +check code for &lt;a href="https://go.dev/doc/articles/race_detector"&gt;races&lt;/a&gt; etc., all
       +with just the &amp;quot;compiler&amp;quot; for the language is excelent. Still probably one of
       +the best developer experiences I know in any programming language.&lt;/p&gt;
       +&lt;p&gt;I will not even talk about the things that everyone talks about Go, like
       +&lt;a href="https://go.dev/doc/effective_go#goroutines"&gt;goroutines&lt;/a&gt;, because I just don't
       +think I can add anything interesting to the topic.&lt;/p&gt;
       +&lt;p&gt;Now for the parts that I like less, the test part still quirks me that it is
       +not based in assertions, but thankfully it is easy to write assertions with
       +generics nowadays:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="nx"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;want&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;want&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;got: %#v, want: %#v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;want&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;GreaterOrEqual&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="nx"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ordered&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;got: %v; want: &amp;gt;=%v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// etc...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just one of those things that I end up re-writing in every project. Yes, I know
       +about &lt;a href="https://github.com/stretchr/testify"&gt;testify&lt;/a&gt; and other assertion
       +libraries, but quoting &lt;a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;amp;t=568s"&gt;Rob
       +Pike&lt;/a&gt; here, &amp;quot;a little
       +copying is better than a little dependency&amp;quot;. As long the code you write is
       +trivial, it is better to duplicate the code than try to import a dependency.&lt;/p&gt;
       +&lt;p&gt;About another piece of code that generics allows me to write and I always end
       +up re-writing in every project is the &lt;code&gt;must*&lt;/code&gt; family of functions:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;must1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nf"&gt;must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;must2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T1&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T2&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt; &lt;span class="nx"&gt;T1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v2&lt;/span&gt; &lt;span class="nx"&gt;T2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nf"&gt;must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v2&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// must3, must4, etc...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Those functions are so useful, especially for scripts where I generally don't
       +want to handle each error: if I have an error, I want the program to halt and
       +print a stack trace (exactly as I would have with a language with exceptions).
       +It basically allow me to convert code from:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nf"&gt;must1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This brings Go closer to Python to me, and I think for scripts this is
       +something great.&lt;/p&gt;
       +&lt;p&gt;Finally, for the things that I hate, well the biggest one currently is the lack
       +of nullability (or in Go terms,
       +&lt;a href="https://github.com/golang/go/issues/49202"&gt;nillability&lt;/a&gt;). After using
       +languages that has it, like Kotlin, or even something like
       +&lt;a href="https://www.mypy-lang.org/"&gt;mypy&lt;/a&gt;, this is one of those things that completely
       +changes the developer experience. I also still don't like the error handling
       +(but &lt;code&gt;must*&lt;/code&gt; goes far by improving the situation, when it is possible to use
       +it), especially because it is easy to lose context on it:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// bad
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readFileContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// good
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readFileContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;readFileContents: error while reading a file: %w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I also have some grips about the mutate everything approach of the language. I
       +prefer immutability by default, but I find that in general as long as you split
       +your functions at a reasonable size it is generally fine.&lt;/p&gt;
       +&lt;p&gt;And for a language that is supposed to be straightforward, it is strange on how
       +much magic the language relies on, in the form of things like &lt;a href="https://go.dev/doc/modules/layout"&gt;&lt;code&gt;internal&lt;/code&gt; and
       +&lt;code&gt;main&lt;/code&gt;&lt;/a&gt; packages, name capitalisation to
       +indicate visibility (&lt;code&gt;private&lt;/code&gt; vs &lt;code&gt;Public&lt;/code&gt;), conditional compiling by filenames
       +(e.g.: &lt;code&gt;foo_amd64.go&lt;/code&gt;, &lt;code&gt;bar_linux.go&lt;/code&gt;), magic comments (e.g.: &lt;code&gt;//go:build&lt;/code&gt;),
       +etc.&lt;/p&gt;
       +&lt;p&gt;I expect to write more Go code going forward. Not because it is the perfect
       +language or whatever, but just because it a is language that has some really
       +good qualities that makes the language attractive even with the issues that I
       +have. That makes it a reasonable good language, and at least for me this is
       +good enough.&lt;/p&gt;</summary></entry><entry><title>Quick bits: nix-shell is cursed</title><link href="https://kokada.dev/blog/quick-bits-nix-shell-is-cursed/" rel="alternate"/><published>2024-07-27T00:00:00+00:00</published><id>https://kokada.dev/blog/quick-bits-nix-shell-is-cursed/</id><summary type="html">&lt;p&gt;The other day I had to run a PHP project in my machine. I have no idea how PHP
       +ecosystem work, I just wanted to get it to run.&lt;/p&gt;
       +&lt;p&gt;The easiest way to get a script to run if you use Nix is to use &lt;code&gt;nix-shell&lt;/code&gt;. As
       +many of you probably know, you can add &lt;code&gt;nix-shell&lt;/code&gt; as a shebang in your scripts
       +to run them as &lt;code&gt;./script&lt;/code&gt;. This was a PHP script so I wanted to do the same.
       +Easy right?&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!/use/bin/env nix-shell
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!nix-shell -i php -p php83
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strict_types&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;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./index.php
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Fatal error: strict_types declaration must be the very first statement in the script in index.php on line 4
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So it seems that &lt;code&gt;declare(strict_types=1)&lt;/code&gt; needs to be the first line in a PHP
       +script if used. I removed &lt;code&gt;declare(strict_types=1)&lt;/code&gt; and while the script works,
       +I don't have enough expertise in PHP to know if this would be safe or not.&lt;/p&gt;
       +&lt;p&gt;I decided to try something that initially looked really dumb:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!/use/bin/env nix-shell
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strict_types&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;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!nix-shell -i php -p php83
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./index.php
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Works
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wat? I mean, it is not dumb if it works, but this at least looks cursed.&lt;/p&gt;
       +&lt;p&gt;Eventually I found this
       +&lt;a href="https://github.com/NixOS/nix/issues/2570#issuecomment-446220517"&gt;comment&lt;/a&gt; in a
       +Nix issue talking about cases where &lt;code&gt;nix-shell&lt;/code&gt; shebang doesn't work. It looks
       +like the classic case of a &lt;a href="https://github.com/NixOS/nix/issues/2570#issuecomment-446222206"&gt;bug that becomes a
       +feature&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; after posting this in
       +&lt;a href="https://lobste.rs/s/gkcgza/quick_bits_nix_shell_is_cursed"&gt;Lobte.rs&lt;/a&gt;, it seems
       +someone decided to open a &lt;a href="https://github.com/NixOS/nix/pull/11202"&gt;Pull
       +Request&lt;/a&gt; to document this behavior.
       +Also the equivalent for the new &lt;a href="https://nix.dev/manual/nix/2.23/command-ref/new-cli/nix#shebang-interpreter"&gt;nix
       +CLI&lt;/a&gt;
       +explicitly documents this behavior:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Note that the &lt;code&gt;#! nix&lt;/code&gt; lines don't need to follow after the first line, to
       +accomodate other interpreters.&lt;/p&gt;
       +&lt;/blockquote&gt;</summary></entry><entry><title>Using GitHub as a (bad) blog platform</title><link href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform/" rel="alternate"/><published>2024-07-26T00:00:00+00:00</published><id>https://kokada.dev/blog/using-github-as-a-bad-blog-platform/</id><summary type="html">&lt;p&gt;I finally started a new blog, thanks to the offer of
       +&lt;a href="https://gluer.org/"&gt;@ratsclub&lt;/a&gt; to give me free access to
       +&lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt;. But considering how small this blog
       +platform is supposed to be, I want to have at least somewhere to have a backup
       +of my posts. I know &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt;, the blog platform that
       +&lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt; runs, has automatic e-mail backups, but
       +I want something more reliable.&lt;/p&gt;
       +&lt;p&gt;I am writing all my posts in Markdown (the format that &lt;a href="https://1.mataroa.blog/guides/markdown/"&gt;Mataroa
       +supports&lt;/a&gt;) files inside
       +&lt;a href="https://neovim.io/"&gt;neovim&lt;/a&gt; anyway, so why not store all my Markdown files in
       +Git? So this is what I did, I now have an unofficial mirror in
       +&lt;a href="https://github.com/thiagokokada/blog"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;While I am here, why not overcomplicate? Can I make an usable blog platform
       +from GitHub? And by that I don't mean GitHub pages, the repository itself. I
       +mean, it already renders Markdown files by default, so no need to do anything
       +in that space. To reach feature parity with
       +&lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt;, I only need to have an index and RSS
       +(since comments are not supported anyway). No need for newsletter since GitHub
       +has a &lt;a href="https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/managing-subscriptions-for-activity-on-github/viewing-your-subscriptions"&gt;watch
       +feature&lt;/a&gt;
       +already.&lt;/p&gt;
       +&lt;p&gt;After a couple of hours hacking a Python script, you can see the result of this
       +monstrosity &lt;a href="https://github.com/thiagokokada/blog"&gt;here&lt;/a&gt;. The script, called
       +&lt;code&gt;gen_blog.py&lt;/code&gt;, is available at the same repository (here is a
       +&lt;a href="https://github.com/thiagokokada/blog/blob/c8986d1ab1b94c0986fd814629bb8eb4034fb6e7/gen_blog.py"&gt;permalink&lt;/a&gt;).
       +It automatically generates an index at
       +&lt;a href="https://github.com/thiagokokada/blog/blob/main/README.md"&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; with
       +each blog post and a
       +&lt;a href="https://raw.githubusercontent.com/thiagokokada/blog/main/rss.xml"&gt;&lt;code&gt;rss.xml&lt;/code&gt;&lt;/a&gt;
       +file at the root of the repository.&lt;/p&gt;
       +&lt;p&gt;Instead of trying to explain the code, I am going to explain the general idea,
       +because I think that if you want to replicate this idea it is better to rewrite
       +it in a way that you understand. It shouldn't take more than 2 hours in any
       +decent programming language. But if you really want, the script itself is
       +licensed in &lt;a href="https://en.wikipedia.org/wiki/WTFPL"&gt;WTFPL&lt;/a&gt; license. The code only
       +uses Python 3's standard library and should work in any relatively recent
       +version (anything newer than 3.9 should work).&lt;/p&gt;
       +&lt;p&gt;So the idea is basically to organise the repository and the Markdown files in a
       +easy way that makes it trivial to parse in a deterministic way. For example, my
       +repository is organised in the following way:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;.
       +├── 2024-07-26
       +│   ├── 01-writing-nixos-tests-for-fun-and-profit.md
       +│   └── 02-using-github-as-a-bad-blog-platform.md &amp;lt;- this file
       +├── gen_blog.py
       +├── README.md
       +└── rss.xml
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Each day that you write a new blog post will be on its own directory. This is
       +nice because Markdown files may include extra files than the posts themselves,
       +e.g.: images, and this organisation make it trivial to organise everything.&lt;/p&gt;
       +&lt;p&gt;Each post has its own Markdown file. I put a two digit number before each post,
       +to ensure that when publishing multiple posts at the same day I keep them in
       +the same order of publishing. But if you don't care about it, you can just name
       +the files whatever you want.&lt;/p&gt;
       +&lt;p&gt;Also, I am assuming that each Markdown file has a header starting with &lt;code&gt;# &lt;/code&gt;,
       +and that is the title of the blog post.&lt;/p&gt;
       +&lt;p&gt;Using the above organisation, I have this function that scrapes the repository
       +and collect the necessary information to generate the index and RSS files:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;grab_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pwd&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pwd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Ignore non-directories or hidden files&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Try to parse date from directory name&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="nb"&gt;print&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;#34;WARN: ignoring non-date directory: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Iterate between the files in the date directory&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# Ignore non-markdown files or hidden files (draft)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.md&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# Grab the first H1 section to parse as title&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;mTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;# (?P&amp;lt;title&amp;gt;.*)\r?\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mTitle&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mTitle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupdict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;title&amp;#34;&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="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;print&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;#34;WARN: did not find title for file: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some interesting tidbits: if a Markdown file has a &lt;code&gt;.&lt;/code&gt; at the start I assume it
       +is a draft post, and ignore it from my scraper. I added a bunch of &lt;code&gt;WARN&lt;/code&gt;
       +prints to make sure that the me in the future doesn't do anything dumb. Also,
       +sorting in reverse since reverse chronological order is the one most people
       +expect in blogs (i.e.: more recent blog posts at top).&lt;/p&gt;
       +&lt;p&gt;After running the function above, I have a resulting dictionary that I can use
       +to generate either a &lt;code&gt;README.md&lt;/code&gt; file or Markdown:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen_readme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;titles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dayPosts&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dayPosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# This creates a relative link to the Markdown file, .e.g.:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# ./02-using-github-as-a-bad-blog-platform.md&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# This formats the title, e.g.:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# - [Using GitHub as a (bad) blog platform](./2024-07-26/02-using-github-as-a-bad-blog-platform.md) - 2024-07-26&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&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;#34;- [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) - %Y-%m-%d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# This appends to the list to generate the content later&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;titles&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# README_TEMPLATE is a string with the static part of the README&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;README_TEMPLATE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;titles&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen_rss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Got most of the specification from here:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# https://www.w3schools.com/XML/xml_rss.asp&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;rss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rss&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Here are the RSS metadata for the blog itself&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;channel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kokada&amp;#39;s blog&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;link&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/thiagokokada/blog&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dd if=/dev/urandom of=/dev/brain0&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# You create one item for each blog post&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dayPost&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dayPost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;item&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RSS_POST_LINK_PREFIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;guid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;link&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pubDate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%a&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt; %b %Y %H:%M:%S GMT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Generate the XML and indent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElementTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;tree&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="s2"&gt;&amp;#34;rss.xml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xml_declaration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To publish a new Post, a basically write a Markdown file, run `./gen_readme.py&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;README.md` at the root of the repository, and see the magic happen.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;It works much better than I initially anticipated. The &lt;code&gt;README.md&lt;/code&gt; is properly
       +populated with the titles and links. The RSS is kind of empty since it has no
       +description, but it seems to work fine (at least in
       +&lt;a href="https://www.inoreader.com/"&gt;Inoreader&lt;/a&gt;, my RSS reader of choice). I can
       +probably fill the post description with more information if I really want, but
       +it is enough for now (update: it is working now, you just need to render the
       +Markdown as HTML and escape the tags;
       +&lt;a href="https://github.com/thiagokokada/blog/blob/9506051cc3d49f203304174b335ff62de9d17a05/gen_blog.py"&gt;permalink&lt;/a&gt;
       +for the updated script). Not sure who is that interested in my writing that
       +will want to use this RSS feed instead the one available in
       +&lt;a href="https://kokada.capivaras.dev/rss/"&gt;capivaras.dev&lt;/a&gt; anyway.&lt;/p&gt;
       +&lt;p&gt;Also, while I am using GitHub here, the same idea would work in GitLab, Gitea,
       +sr.ht or whatever. As long as your source hub supports Markdown files it should
       +work.&lt;/p&gt;
       +&lt;p&gt;So that is it. I am not saying this is a good idea for your primary blog
       +platform or whatever, and I still prefer to publish to a platform that doesn't
       +track users or have tons of JavaScript or whatever. But if you want a backup of
       +your posts and you are already writing Markdown anyway, well, there are worse
       +ways to do it I think.&lt;/p&gt;
       +&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; I rewrote the script again using Go
       +(&lt;a href="https://github.com/thiagokokada/blog/blob/3ece3853e7dd32977e7ec5238d2955e530ca4beb/blog.go"&gt;permalink&lt;/a&gt;).
       +The reason for it is because when I started rendering Markdown (for
       +descriptions) the Python version got quite slow (not the fault of Python
       +itself, mostly because of the usage of &lt;code&gt;nix-shell&lt;/code&gt; to manage dependencies;
       +something that Go doesn't need). Took about half an hour, showing how easy it
       +is to do the same.&lt;/p&gt;</summary></entry><entry><title>Writing NixOS tests for fun and profit</title><link href="https://kokada.dev/blog/writing-nixos-tests-for-fun-and-profit/" rel="alternate"/><published>2024-07-26T00:00:00+00:00</published><id>https://kokada.dev/blog/writing-nixos-tests-for-fun-and-profit/</id><summary type="html">&lt;p&gt;I recently started a &lt;a href="https://github.com/thiagokokada/hyprland-go"&gt;new side
       +project&lt;/a&gt; writing an IPC library in
       +Go for &lt;a href="https://hyprland.org/"&gt;Hyprland&lt;/a&gt;, a Window Manager for Wayland.&lt;/p&gt;
       +&lt;p&gt;Once I got past the Work-in-Progress phase, I realise I had an issue: I wrote
       +some tests, but I was running then inside my system running Hyprland. And the
       +tests themselves were annoying: since they send commands to the current running
       +Hyprland instance, I was having programs being opened and settings being
       +changed, because this was the only way to have a reasonable good confidence
       +that what I was doing was correct. So I need to do like any good developer and
       +implement a CI, but how?&lt;/p&gt;
       +&lt;p&gt;One approach would be to create something like a mock client and test against
       +my mock. Since this mock wouldn't need a running Hyprland instance the tests
       +could run everywhere (even in non-Linux systems!), but they wouldn't be much
       +useful. Mocks are great for testing business logic, but not really for making
       +sure everything is working correctly.&lt;/p&gt;
       +&lt;p&gt;I need something more akin to an integration test, but this is tricky. It is
       +not like I am doing integration with e.g.: PostgreSQL that has thousands of
       +libraries available to make integration tests easier, I am doing integration
       +with a Window Manager that is a moving target with multiple breaking changes in
       +each release. And this is where NixOS tests enter, a way to run tests inside
       +Virtual Machines configured in Nix.&lt;/p&gt;
       +&lt;p&gt;I am a long time NixOS user and commiter, but I never wrote a NixOS test
       +outside of &lt;a href="https://github.com/NixOS/nixpkgs"&gt;nixpkgs&lt;/a&gt; itself. However I knew
       +it was possible, and after doing a quick reading of the &lt;a href="https://wiki.nixos.org/wiki/NixOS_VM_tests"&gt;Wiki
       +entry&lt;/a&gt; about it, I was ready to
       +start.&lt;/p&gt;
       +&lt;p&gt;The first part is to call &lt;code&gt;pkgs.testers.runNixOSTest&lt;/code&gt; and configure the machine
       +as any other NixOS system, e.g.:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;testers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runNixOSTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hyprland-go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# bootloader related configuration&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemd-boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;efi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canTouchEfiVariables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# enable hyprland&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hyprland&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# create a user called alice&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;isNormalUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# add some extra packages that we need during tests&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;kitty&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# auto login as alice&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autologinUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# configure VM, increase memory and CPU and enable OpenGL via LLVMPipe&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;virtualisation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qemu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-smp 2&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-m 4G&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-vga none&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-device virtio-gpu-pci&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# Start hyprland at login&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loginShellInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hyprland&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;testScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;start_all()&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A few details that I want to bring to attention. The first one is how easy it
       +is to setup things like a normal user account, add some extra packages we need
       +for testing, add Hyprland itself and configure auto-login. I have no idea how
       +painful it would be to automatise all those steps in e.g.: Ansible, but here we
       +are in a few lines of Nix code. This is, of course, thanks to all the
       +contributors to nixpkgs that implement something that help their own use case,
       +but once combined make it greater than the sum of the parts.&lt;/p&gt;
       +&lt;p&gt;Second is something that I took a while to figure out: how to enable GPU
       +acceleration inside the VM. You see, Hyprland, different from other Window
       +Managers, requires OpenGL support. This is basically why the flag &lt;code&gt;-device virtio-gpu-pci&lt;/code&gt; is in &lt;code&gt;virtualisation.qemu.options&lt;/code&gt;, this enables OpenGL
       +rendering via LLVMPipe, that while being slow since it is rendered in CPU, is
       +sufficient for this case.&lt;/p&gt;
       +&lt;p&gt;Putting the above code inside a
       +&lt;a href="https://wiki.nixos.org/wiki/Flakes"&gt;&lt;code&gt;flake.nix&lt;/code&gt;&lt;/a&gt; for reproducibility, I had
       +something similar to:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hyprland&amp;#39;s IPC bindings for Go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:NixOS/nixpkgs/nixos-unstable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x86_64-linux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;testVm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;testers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runNixOSTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# the code above&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I can now run &lt;code&gt;nix build .#checks.x86_64-linux.testVm -L&lt;/code&gt; to build and run the
       +VM. However it is not really useful right now, since we didn't add any useful
       +code in &lt;code&gt;testScript&lt;/code&gt;, the core of the NixOS test framework. We can also run
       +&lt;code&gt;nix build .#checks.x86_64-linux.testVm.driverInteractive&lt;/code&gt; and
       +&lt;code&gt;./result/bin/nixos-test-driver&lt;/code&gt;: this will start a Python console where we can
       +manually play with the VM (try typing &lt;code&gt;start_all()&lt;/code&gt; for example).&lt;/p&gt;
       +&lt;p&gt;The &lt;code&gt;testScript&lt;/code&gt; is a sequence of Python statements that perform various
       +actions, such as starting VMs, executing commands in the VMs, and so on. More
       +about it in the official
       +&lt;a href="https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests"&gt;documentation&lt;/a&gt;.
       +For our case we can start with something like this:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;testScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* python */&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      start_all()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      machine.wait_for_unit(&amp;#34;multi-user.target&amp;#34;)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      machine.wait_for_file(&amp;#34;/home/alice/test-finished&amp;#34;)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first statement, &lt;code&gt;start_all()&lt;/code&gt;, starts all VMs, in this case we have only
       +one, called &lt;code&gt;machine&lt;/code&gt;. We send two further commands to &lt;code&gt;machine&lt;/code&gt;:
       +&lt;code&gt;wait_for_unit(&amp;quot;multi-user.target&amp;quot;)&lt;/code&gt; and
       +&lt;code&gt;wait_for_file(&amp;quot;/home/alice/test-finished&amp;quot;)&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;The first command waits until systemd's &lt;code&gt;multi-user.target&lt;/code&gt; is ready, a good
       +way to ensure that the system is ready for further commands. The second one we
       +wait for a file called &lt;code&gt;test-finished&lt;/code&gt; to appear in Alice's &lt;code&gt;$HOME&lt;/code&gt; (basically,
       +a canary), but how can we generate this file?&lt;/p&gt;
       +&lt;p&gt;Remember that we added &lt;code&gt;programs.bash.loginShellInit = &amp;quot;Hyprland&amp;quot;&lt;/code&gt;, that
       +automatically starts Hyprland when Alice logs in. We need to modify that
       +command to run the Go tests from our library. The good thing is that Hyprland
       +configuration file supports a
       +&lt;a href="https://wiki.hyprland.org/Configuring/Keywords/#executing"&gt;&lt;code&gt;exec-once&lt;/code&gt;&lt;/a&gt;
       +command that runs a command during Hyprland launch. We can abuse this to launch
       +a terminal emulator and run our tests:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loginShellInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;testScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeShellScript&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hyprland-go-test&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        set -euo pipefail
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        trap &amp;#39;echo $? &amp;gt; $HOME/test-finished&amp;#39; EXIT # creates the canary when the script finishes
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        cd &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="sr"&gt;./.&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; # go to the library directory
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        go test -v ./... &amp;gt; $HOME/test.log 2&amp;gt;&amp;amp;1 # run Go tests
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;hyprlandConf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeText&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hyprland.conf&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        exec-once = kitty sh -c &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;testScript&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      Hyprland --config &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;hyprlandConf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we are basically creating a custom Hyprland config that starts a
       +&lt;a href="https://sw.kovidgoyal.net/kitty/"&gt;Kitty&lt;/a&gt; terminal emulator, that then launches
       +a shell script that runs the test. Since we have no way to get the results of
       +the test, we pipe the output to a file that we can collect later (e.g.:
       +&lt;code&gt;machine.succeded(&amp;quot;cat /home/alice/test.log&amp;quot;)&lt;/code&gt;). And once the script exit, we
       +create the canary file &lt;code&gt;$HOME/test-finished&lt;/code&gt;, that allows the &lt;code&gt;testScript&lt;/code&gt;
       +knows that the test finished and it can destroy the VM safely.&lt;/p&gt;
       +&lt;p&gt;If you want to take a look at the final result, it is
       +&lt;a href="https://github.com/thiagokokada/hyprland-go/blob/v0.0.1/flake.nix"&gt;here&lt;/a&gt;. This
       +tests run in any Linux machine that supports KVM, and also works in &lt;a href="https://github.com/thiagokokada/hyprland-go/actions/workflows/nix.yaml"&gt;GitHub
       +Actions&lt;/a&gt;
       +thanks to the the
       +&lt;a href="https://github.com/DeterminateSystems/nix-installer-action"&gt;nix-installer-action&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;And now I have a proper CI pipeline in a way that I never imagined would be
       +possible, especially considering how simple it was.&lt;/p&gt;</summary></entry></feed>
       +\ No newline at end of file
 (DIR) diff --git a/input/sfeed/realworld/kokada.dev.rss.xml b/input/sfeed/realworld/kokada.dev.rss.xml
       @@ -0,0 +1,3706 @@
       +<?xml version="1.0" encoding="utf-8"?>
       +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>kokada</title><link>https://kokada.dev</link><description>
       +# dd if=/dev/urandom of=/dev/brain0
       +dd: error writing &amp;#39;/dev/brain0&amp;#39;: No space left on device
       +
       +</description><atom:link href="https://kokada.dev/rss/" rel="self"/><language>en-us</language><lastBuildDate>Wed, 04 Dec 2024 00:00:00 +0000</lastBuildDate><item><title>Praise to scdoc to generate man pages</title><link>https://kokada.dev/blog/praise-to-scdoc-to-generate-man-pages/</link><description>&lt;p&gt;Hey, its been a long time since my &lt;a href="posts/2024-10-07/01-enabling-le-audio-lc3-in-wf-1000xm5.md"&gt;last blog
       +post&lt;/a&gt;. It is mostly
       +because I ran out of things to write, but I expected this. This is probably
       +more likely how I am actually going to post from now. At least, it shows that
       +my plan to have a &lt;a href="https://kokada.dev/blog/making-a-blog-for-the-next-10-years"&gt;blog for a long
       +time&lt;/a&gt;, that is
       +easy to go back when I wanted is working fine, but I digress.&lt;/p&gt;
       +&lt;p&gt;Going back to the theme of the today blog post, I needed to write a &lt;a href="https://en.wikipedia.org/wiki/Man_page"&gt;man
       +page&lt;/a&gt; for the first time in years. I
       +hate &lt;a href="https://en.wikipedia.org/wiki/Troff"&gt;troff&lt;/a&gt;, the typesetting system used
       +for man pages (similar to &lt;a href="https://en.wikipedia.org/wiki/LaTeX"&gt;LaTeX&lt;/a&gt; for
       +documents). It is one of the weirdest languages that I ever saw, and even the
       +example in Wikipedia shows that:&lt;/p&gt;
       +&lt;pre&gt;&lt;code class="language-troff"&gt;.ND &amp;quot;January 10, 1993&amp;quot;
       +.AU &amp;quot;Ms. Jane Smith&amp;quot;
       +.AT &amp;quot;Upcoming appointment&amp;quot;
       +.MT 5
       +.DS
       +Reference #A12345
       +.sp 4
       +Mr. Samuel Jones
       +Field director, Bureau of Inspections
       +1010 Government Plaza
       +Capitoltown, ST
       +.sp 3
       +Dear Mr. Jones,
       +.sp 2
       +.P
       +Making reference to the noted obligation to submit for state inspection our newly created production process, we request that you consider the possible inappropriateness of subjecting the  innovative technologies of tomorrow to the largely antiquated requirements of yesterday.  If our great state is to prosper in the twenty-first century, we must take steps
       +.B now ,
       +in
       +.I this
       +year of
       +.I this
       +decade, to prepare our industrial base for the interstate and international competition that is sure to appear.  Our new process does precisely that.  Please do not let it be undone by a regulatory environment that is no longer apt.
       +.P
       +Thank you for your consideration of our position.
       +.FC Sincerely
       +.SG
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Keep in mind that the break lines are necessary every time you introduce a
       +macro, like &lt;code&gt;.I this&lt;/code&gt; (that I &lt;em&gt;think&lt;/em&gt; it is for italics). Yes, this format is
       +as illegible as hell, and it is worse that the format lacks good tooling (or at
       +least I didn't find any good ones).&lt;/p&gt;
       +&lt;p&gt;Most people when they need to write a man page nowadays ends up using some
       +other format that generates a man page. For example, in the past I used
       +&lt;a href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt; to convert Markdown to a man page, but even if
       +Pandoc is a great project the result is sub-optimal at best: Markdowns are, at
       +the end, designed for generating HTML (and a subset of it), and not man pages,
       +so you basically ends up fighting the format for it to do what you want.
       +Also, Pandoc is a big project, with a ~200MB binary (at least it is the default
       +Pandoc binary in Nix).&lt;/p&gt;
       +&lt;p&gt;For this specific project I needed something small. I am trying to replace one
       +of the most essential pieces inside NixOS, &lt;code&gt;nixos-rebuild&lt;/code&gt;, written in Bash,
       +with a &lt;a href="https://discourse.nixos.org/t/nixos-rebuild-ng-a-nixos-rebuild-rewrite/55606/"&gt;full rewritten in
       +Python&lt;/a&gt;
       +(sorry Rust zealots!), called &lt;code&gt;nixos-rebuild-ng&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;Since this project will eventually (if successful) be in the critical path for
       +NixOS, I want to reduce the number of dependencies as much as possible, so
       +something as big as Pandoc is out. I could use
       +&lt;a href="https://asciidoc.org/"&gt;AsciiDoc&lt;/a&gt;, but it is a big complicated Python project
       +(this may seem ironic, but &lt;code&gt;nixos-rebuild-ng&lt;/code&gt; has only one runtime dependency,
       +that is optional). And I also hated the last time I tried to use it to generate
       +man pages: it more flexible than Markdown, but still far from optimal.&lt;/p&gt;
       +&lt;p&gt;Thanks to Drew DeVault (creator of &lt;a href="https://swaywm.org/"&gt;SwayWM&lt;/a&gt;) that seems it
       +had the same issues in the past and created
       +&lt;a href="https://drewdevault.com/2018/05/13/scdoc.html"&gt;&lt;code&gt;scdoc&lt;/code&gt;&lt;/a&gt;, a very simple man
       +page generator using a DSL inspired in Markdown, but specific to generate man
       +pages. The binary is written in C (and advantage in this case since it means it
       +is easier to bootstrap), is small (~1 Kloc) and has no dependencies, so it
       +fits the requirement.&lt;/p&gt;
       +&lt;p&gt;While the language suffers from being a niche project for a niche segment, the
       +&lt;a href="https://man.archlinux.org/man/scdoc.5.en"&gt;man page&lt;/a&gt; for it is actually really
       +nice. It is terse though and lacks examples, and this is what this blog post
       +will try to accomplish.&lt;/p&gt;
       +&lt;p&gt;To start, let's have a quick summary of the syntax, written in &lt;code&gt;scdoc&lt;/code&gt; as
       +comments:&lt;/p&gt;
       +&lt;pre&gt;&lt;code class="language-scdoc"&gt;; quick summary:
       +; # new section
       +; comments starts with ;
       +; - this is a list
       +;         - sub-list
       +; - *bold*: _underline_, force a line break++
       +; - [tables], \[ can be used to force an actual [
       +; . numbered list
       +; please configure your editor to use hard tabs
       +; see `man 5 scdoc` for more information about syntax
       +; or https://man.archlinux.org/man/scdoc.5.en
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;I actually added this summary in the &lt;code&gt;.scd&lt;/code&gt; (the &lt;code&gt;scdoc&lt;/code&gt; extension) files that
       +I wrote, so it is easy for someone that never saw the format to start
       +collaborating.&lt;/p&gt;
       +&lt;p&gt;And here an example of a (summarised) man page in &lt;code&gt;.scd&lt;/code&gt; format:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nixos-rebuild-ng(8)
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# NAME
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nixos-rebuild - reconfigure a NixOS machine
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# SYNOPSIS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;_nixos-rebuild_&lt;/span&gt; \[--upgrade] [--upgrade-all]++
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                \[{switch,boot}]
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# DESCRIPTION
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This command has one required argument, which specifies the desired operation.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It must be one of the following:
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;*switch*&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Build and activate the new configuration, and make it the boot default.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        That is, the configuration is added to the GRUB boot menu as the
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        default menu entry, so that subsequent reboots will boot the system
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        into the new configuration. Previous configurations activated with
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        nixos-rebuild switch or nixos-rebuild boot remain available in the GRUB
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        menu.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;*boot*&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Build the new configuration and make it the boot default (as with
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ge"&gt;*nixos-rebuild switch*&lt;/span&gt;), but do not activate it. That is, the system
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        continues to run the previous configuration until the next reboot.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# OPTIONS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ge"&gt;*--upgrade, --upgrade-all*&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Update the root user&amp;#39;s channel named &amp;#39;nixos&amp;#39; before rebuilding the
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        system.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        In addition to the &amp;#39;nixos&amp;#39; channel, the root user&amp;#39;s channels which have
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        a file named &amp;#39;.update-on-nixos-rebuild&amp;#39; in their base directory will
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        also be updated.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Passing &lt;span class="ge"&gt;*--upgrade-all*&lt;/span&gt; updates all of the root user&amp;#39;s channels.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;See the Nix manual, &lt;span class="ge"&gt;*nix flake lock --help*&lt;/span&gt; or &lt;span class="ge"&gt;*nix-build --help*&lt;/span&gt; for details.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# ENVIRONMENT
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NIXOS_CONFIG
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        Path to the main NixOS configuration module. Defaults to
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ge"&gt;_/etc/nixos/configuration.nix_&lt;/span&gt;.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# FILES
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/etc/nixos/flake.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        If this file exists, then &lt;span class="ge"&gt;*nixos-rebuild*&lt;/span&gt; will use it as if the
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ge"&gt;*--flake*&lt;/span&gt; option was given. This file may be a symlink to a
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        flake.nix in an actual flake; thus &lt;span class="ge"&gt;_/etc/nixos_&lt;/span&gt; need not be a
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        flake.
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# AUTHORS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Nixpkgs/NixOS contributors
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is a screenshot of the result:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-12-04/2024-12-04-230955_hyprshot.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-12-04/2024-12-04-230955_hyprshot.png" alt="Man page rendered from scd
       +file"&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;One of nice things that I found is how looking at the plain text looks kind
       +like the man page result already. And if you know Markdown, you can basically
       +understand most things that is happening. There are a few differences, like
       +&lt;code&gt;*bold*&lt;/code&gt; instead of &lt;code&gt;**bold**&lt;/code&gt;, and while they're unfortunate they're not the
       +end of the world.&lt;/p&gt;
       +&lt;p&gt;Now, the format has its quirks. The first line being the name of the program
       +and section in parenthesis is required, but this makes sense, since you need
       +this information for the corners. But for one, it requires the usage of hard
       +tabs to create indentation, and the error messages are awful, in a situation
       +that kind remembers me of &lt;code&gt;Makefile&lt;/code&gt;. Also the choice of &lt;code&gt;[&lt;/code&gt; to start a table
       +means that the traditional &lt;code&gt;app [command]&lt;/code&gt; needs in many cases to be escaped as
       +&lt;code&gt;app \[command]&lt;/code&gt;. I found this a strange choice since this is supposed to be a
       +format that is only used for man pages, and using &lt;code&gt;[command]&lt;/code&gt; to indicate an
       +optional is common, but at least it is easy to escape.&lt;/p&gt;
       +&lt;p&gt;In the end, I think all that matters is the result. And for the first time for
       +all those years trying to write a man page, I am satisfied with the result. The
       +man page looks exactly as I wanted once rendered, and the &lt;code&gt;.scd&lt;/code&gt; file looks
       +reasonable good that it can work as a documentation for someone that for one
       +reason or another can't use the man page (can't say the same for the troff
       +version). Also, it is really easy for someone to just go there and update the
       +man page, even without experience in the format (except for maybe the
       +requirement of tabs). So all in all, I really liked the format, and will use it
       +again if I need to write another man page in the future.&lt;/p&gt;</description><pubDate>Wed, 04 Dec 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/praise-to-scdoc-to-generate-man-pages/</guid></item><item><title>Enabling LE Audio/LC3 in WF-1000XM5</title><link>https://kokada.dev/blog/enabling-le-audio-lc3-in-wf-1000xm5/</link><description>&lt;p&gt;One of things that I hate the most about the fact that we are all using
       +wireless earbuds instead of wired earphones is the latency: it is bad, getting
       +up to seconds(!) depending on your particular combination of OS/earbuds/device.&lt;/p&gt;
       +&lt;p&gt;There is a solution though: Bluetooth LE Audio, that is supposed to fix
       +multiple issues with the original design for Bluetooth Classic Audio, including
       +a much lower latency, improved efficiency (e.g.: less battery power) and even
       +multiple streams of audio. LE Audio also includes a new default codec for
       +improved audio quality, &lt;a href="https://en.wikipedia.org/wiki/LC3_(codec)"&gt;LC3&lt;/a&gt;, that
       +replaces the venerable &lt;a href="https://en.wikipedia.org/wiki/SBC_(codec)"&gt;SBC&lt;/a&gt; codec
       +for audio.&lt;/p&gt;
       +&lt;p&gt;However, the standard is a mess right now: a few wireless headphones already
       +support it, but they're generally disabled by default and it is pretty messy to
       +enable. And even after enabling it, getting it to work can be a pain.&lt;/p&gt;
       +&lt;p&gt;I have pretty much the best setup to use LE Audio right now: a recently
       +released Pixel 9 Pro with Sony's
       +&lt;a href="https://www.sony.ie/headphones/products/wf-1000xm5"&gt;WF-1000XM5&lt;/a&gt; earbuds, and
       +after lots of tries I got it to work. You can see below the versions of
       +everything I am using:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;Android: 14&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.sony.songpal.mdr"&gt;Sound
       +Connect&lt;/a&gt;:
       +11.0.1&lt;/li&gt;
       +&lt;li&gt;WM-1000XM5: 4.0.2&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;The first thing you need to do is enable in &amp;quot;Sound Connect&amp;quot; app &amp;quot;LE Audio
       +Priority&amp;quot; in &amp;quot;Device Settings -&amp;gt; System&amp;quot;:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642922_y.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642922_y.jpg" alt="LE Audio option inside Sound
       +Connect"&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;After this, you will need to pair your headset with the device again. You can
       +do this as same as always: press and hold the button in case for a few seconds
       +until a blue light starts to blink. However, this is where things starts to get
       +janky: I couldn't get the headset to pair with Android again.&lt;/p&gt;
       +&lt;p&gt;A few of the things that I needed to do (in no specific order):&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;Remove the previous paired headset&lt;/li&gt;
       +&lt;li&gt;Restart the Android&lt;/li&gt;
       +&lt;li&gt;Clean-up &amp;quot;Sound Connect&amp;quot; storage (Long press the app icon -&amp;gt; &amp;quot;App info&amp;quot; -&amp;gt;
       +&amp;quot;Storage and Cache&amp;quot; -&amp;gt; &amp;quot;Clear storage&amp;quot;)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;If you can get the headset to connect, go to the &amp;quot;Bluetooth&amp;quot; settings in
       +Android, click in the gear icon for the headset and enable &amp;quot;LE Audio&amp;quot; option:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642937_y.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-10-07/photo_4909454744305642937_y.jpg" alt="LE Audio option Bluetooth
       +Settings"&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;If you can't, you may want to &lt;a href="https://helpguide.sony.net/mdr/2963/v1/en/contents/TP1000783925.html"&gt;restore the headset to factory
       +settings&lt;/a&gt;
       +and try again from the start (that means pairing your device with &amp;quot;Sound
       +Connect&amp;quot; again, and you may want to try to clear the storage before doing so).&lt;/p&gt;
       +&lt;p&gt;Yes, the process is extremely janky, but I think this is why both &amp;quot;Sound
       +Connect&amp;quot; and Android marks this feature as beta/experimental. And I still need
       +to test the latency, but from my initial testing there are some glitches when
       +the audio is only used for a short period of time (e.g.: Duolingo only enables
       +the audio when the character is speaking). So I only recommend this if you want
       +to test how LE Audio will behave, since it is clear that this needs more
       +polish.&lt;/p&gt;</description><pubDate>Mon, 07 Oct 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/enabling-le-audio-lc3-in-wf-1000xm5/</guid></item><item><title>Quick bits: where does Nix store Flake's trusted settings?</title><link>https://kokada.dev/blog/quick-bits-where-does-nix-store-flakes-trusted-settings/</link><description>&lt;p&gt;Have you ever run a &lt;code&gt;nix build&lt;/code&gt; command and had this prompt?&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix run .#darwinActivations/Sekai-MacBook-Pro
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;do you want to allow configuration setting &amp;#39;extra-substituters&amp;#39; to be set to &amp;#39;https://nix-community.cachix.org https://thiagokokada-nix-configs.cachix.org&amp;#39; (y/N)? y
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;do you want to permanently mark this value as trusted (y/N)? y
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And realise that you did/didn't want to mark this value as trusted? But where
       +is this stored? Well, I had to look at the Nix source code to find the answer,
       +since I can't find this anywhere in the web or in ChatGPT (but I am sure that
       +now that I posted this it will eventually be searchable), and the answer can be
       +found
       +&lt;a href="https://github.com/NixOS/nix/blob/c116030605bf7fecd232d0ff3b6fe066f23e4620/src/libflake/flake/config.cc#L13-L16"&gt;here&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="nf"&gt;trustedListPath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getDataDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/trusted-settings.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Where is &lt;code&gt;getDataDir()&lt;/code&gt; though? I found the answer
       +&lt;a href="https://github.com/NixOS/nix/blob/c116030605bf7fecd232d0ff3b6fe066f23e4620/src/libutil/users.cc#L52-L65"&gt;here&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="nf"&gt;getDataDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;NIX_DATA_HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;xdgDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_DATA_HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xdgDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xdgDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getHome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/.local/share/nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we solved the mystery:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;If &lt;code&gt;NIX_DATA_HOME&lt;/code&gt; is set, the file will be in
       +&lt;code&gt;$NIX_DATA_HOME/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
       +&lt;li&gt;If &lt;code&gt;XDG_DATA_HOME&lt;/code&gt; is set, the file will be in
       +&lt;code&gt;$XDG_DATA_HOME/nix/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
       +&lt;li&gt;Otherwise Nix will fallback to &lt;code&gt;$HOME/.local/share/nix/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;By the way, if you don't know why you got this prompt, if &lt;code&gt;flake.nix&lt;/code&gt; has a
       +&lt;code&gt;nixConfig&lt;/code&gt; attribute inside &lt;code&gt;outputs&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; this &lt;code&gt;nixConfig&lt;/code&gt; is an unsafe
       +attribute (like &lt;code&gt;extra-substituters&lt;/code&gt;) you will get this prompt, unless you set
       +&lt;a href="https://nix.dev/manual/nix/2.23/command-ref/conf-file#conf-accept-flake-config"&gt;&lt;code&gt;accept-flake-config = true&lt;/code&gt;&lt;/a&gt;
       +in your Nix configuration (please &lt;strong&gt;do not do this&lt;/strong&gt;, it is dangerous because
       +it may allow running possible unsafe options without asking you first).&lt;/p&gt;
       +&lt;p&gt;You can inspect the JSON file or delete it and Nix will prompt you again the
       +next time you run a &lt;code&gt;nix&lt;/code&gt; command. And yes, saving this preference is
       +controversial considering this is Nix, but having the power to set &lt;code&gt;nixConfig&lt;/code&gt;
       +attributes is really powerful, and with great powers comes great
       +responsibilities.&lt;/p&gt;</description><pubDate>Mon, 30 Sep 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-where-does-nix-store-flakes-trusted-settings/</guid></item><item><title>My coding experience with LLM</title><link>https://kokada.dev/blog/my-coding-experience-with-llm/</link><description>&lt;p&gt;I don't want to talk about AI. AI fatigue is real, with (garbage) AI content
       +everywhere you go. However, I like to talk about tools, and AIs, or more
       +specifically, &lt;a href="https://en.wikipedia.org/wiki/Large_language_model"&gt;Large Language
       +Models&lt;/a&gt; (LLM), can be
       +really useful tools. Maybe someday we will have our AI overlords dominating
       +everything (including my job), but right now I don't feel particularly
       +threatened given my experience with AIs.&lt;/p&gt;
       +&lt;p&gt;Given that &lt;a href="https://mashable.com/article/google-search-low-quality-research"&gt;Google Search is getting
       +worse&lt;/a&gt;, I am
       +slowly relying in LLMs more and more. This is the focus of this post, basically
       +&lt;a href="https://www.rottentomatoes.com/m/the_good_the_bad_and_the_ugly"&gt;the good, the bad and the
       +ugly&lt;/a&gt; (by the
       +way, never saw this film) of LLMs, especially focused for solving coding
       +problems. I will take a look at the my chat history in
       +&lt;a href="https://chatgpt.com/"&gt;ChatGPT&lt;/a&gt; and see where it was a good tool, where it
       +shined, and where it just completely wasted my time.&lt;/p&gt;
       +&lt;p&gt;A disclaimer, I never bothered to buy a ChatGPT subscription because I don't
       +feel that the tool is useful enough (at least yet), so most of the examples
       +here are either with &lt;a href="https://openai.com/index/chatgpt/"&gt;GPT-3.5&lt;/a&gt; or &lt;a href="https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/"&gt;GPT-4o
       +mini&lt;/a&gt;,
       +since those where the models offered by free in ChatGPT.&lt;/p&gt;
       +&lt;p&gt;An addendum, I have some experience with &lt;a href="https://github.com/features/copilot"&gt;GitHub
       +Copilot&lt;/a&gt; (another LLM, but this one runs
       +inside your IDE or favorite text editor) in &lt;code&gt;$PREVIOUS_JOB&lt;/code&gt;. But my experience
       +with it is even more underwhelming than what I am going to describe in this
       +post: it was basically really good for generating fixture for tests and between
       +ok-ish to plain bad for everything else. Yes, that's would fit in a tweet (or
       +xeet?).&lt;/p&gt;
       +&lt;h2&gt;The good&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/c58a1432-2761-4954-acac-cade29942c34"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Is there any way that I can pass a context for a net.conn to cancel its
       +Read()?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This is one of the good cases where ChatGPT felt to me that I was talking with
       +an expert in Go. To start, I didn't even mention in my initial prompt that I
       +was talking about Go, but it correctly inferred so. Also, the solution overview
       +was great:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;ol&gt;
       +&lt;li&gt;&lt;strong&gt;Use a separate goroutine&lt;/strong&gt; to handle the context cancellation.&lt;/li&gt;
       +&lt;li&gt;&lt;strong&gt;Set a deadline&lt;/strong&gt; on the connection using &lt;code&gt;SetReadDeadline()&lt;/code&gt; to unblock
       +the &lt;code&gt;Read()&lt;/code&gt; when the context is canceled.&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Before asking ChatGPT about this issue I had already coded my own naive
       +solution that was something similar to this:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Compared this solution with the one from ChatGPT:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// Start a goroutine to perform the read
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;// Context was canceled or timed out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="c1"&gt;// Wait for the read to finish
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read finished successfully
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The ChatGPT version is much better because:&lt;/p&gt;
       +&lt;ol&gt;
       +&lt;li&gt;It doesn't close the connection socket allowing it to be reused&lt;/li&gt;
       +&lt;li&gt;It waits until the goroutine is finished to avoid leaks&lt;/li&gt;
       +&lt;li&gt;It closes the channel after it finishes&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;p&gt;Now, I don't claim to be a Go expert (far from it), but this was one of those
       +cases where I felt that I was being guided by someone that is coding in Go for
       +years.&lt;/p&gt;
       +&lt;p&gt;But it didn't end there, I asked:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;If I want to reuse the connection, do I need to reset the SetReadDeadline?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Since in this particular case I wanted the underlying connection to be reused,
       +and ChatGPT correctly amended the code:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// Same as before
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;// Context was canceled or timed out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="c1"&gt;// Wait for the read to finish
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Reset the deadline so the connection can be reused
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read finished successfully
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also the explanation on why this happens was good:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;When you set a read deadline with &lt;code&gt;conn.SetReadDeadline(time.Now())&lt;/code&gt; to unblock
       +the &lt;code&gt;Read()&lt;/code&gt; call, this deadline remains in effect for future &lt;code&gt;Read()&lt;/code&gt;
       +operations. This means that if you attempt another read operation after this,
       +it could immediately fail with a timeout error if the deadline is still in
       +effect.
       +To reuse the connection, you'll need to remove the deadline or set a new one.
       +You can do this by calling &lt;code&gt;conn.SetReadDeadline(time.Time{})&lt;/code&gt;, which clears
       +any existing deadline.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;I had one last question that I kind already knew the answer:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Do we need to defer the clearing?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;And ChatGPT correctly returned:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Whether you need to defer the clearing of the read deadline depends on how
       +your code is structured. If you want to ensure that the read deadline is
       +always cleared after a context cancellation, even if the code panics or exits
       +early, then deferring the &lt;code&gt;conn.SetReadDeadline(time.Time{})&lt;/code&gt; call is a good
       +idea.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This whole interaction was one of the best ones I ever had in ChatGPT, one that
       +it gave me an actual functional code with good explanation on what was
       +happening. But still, it didn't talked at all about the gotcha of setting the
       +read deadline and reusing the underlying connection, needing another prompt to
       +give me this information.&lt;/p&gt;
       +&lt;p&gt;Also, there is one issue in ChatGPT solution though: we are discarding the
       +values of the buffer once the context is done by returning &lt;code&gt;n = 0&lt;/code&gt; instead of
       +the number of bytes read. In this particular case this is what I wanted, but
       +this is a possible issue that ChatGPT didn't talk.&lt;/p&gt;
       +&lt;p&gt;For comparison,
       +&lt;a href="https://stackoverflow.com/questions/61637359/how-to-use-context-context-with-tcp-connection-read"&gt;this&lt;/a&gt;
       +is the best that I got searching in both DuckDuckGo and Google, that is where I
       +kind based my first solution on. I still not sure where ChatGPT could give me
       +such a good answer for this question, because I just couldn't find anything
       +remotely similar during my searches.&lt;/p&gt;
       +&lt;h2&gt;The amazing&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/0c9a148f-4cfd-4b4e-aa84-7486e185125b"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;I want to write an extension to &lt;a href="https://github.com/yuin/goldmark/"&gt;https://github.com/yuin/goldmark/&lt;/a&gt; that allows
       +me to rewrite &lt;code&gt;[link](../2024-07-30/post.md)&lt;/code&gt; to
       +&lt;code&gt;[link](https://kokada.capivaras.dev/slug)&lt;/code&gt;&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Here is the generated code:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;linkrewriter&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;strings&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/ast&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/parser&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/renderer&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/util&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// LinkRewriter is the main struct for your extension
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LinkRewriter&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// NewLinkRewriter returns a new instance of LinkRewriter
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;NewLinkRewriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Extend will be called by Goldmark to add your extension
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;Extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AddOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithASTTransformers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Prioritized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Transform is the method that modifies the AST
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LinkRewriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entering&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WalkStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entering&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WalkContinue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="nf"&gt;rewriteLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WalkContinue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// rewriteLink modifies the link URL
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;rewriteLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;../&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Modify this part according to your slug generation logic
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nf"&gt;generateSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://kokada.capivaras.dev/%s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// generateSlug generates a slug from the given URL
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;generateSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Assuming the slug is the last part of the URL without the extension
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="s"&gt;&amp;#34;github.com/yuin/goldmark/renderer/html&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;linkrewriter&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;path/to/your/linkrewriter&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;md&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithExtensions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="nx"&gt;linkrewriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewLinkRewriter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithRendererOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHardWraps&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;),&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Use `md.Convert` to convert your markdown to HTML as usual
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This one will need some context. &lt;a href="https://github.com/yuin/goldmark/"&gt;Goldmark&lt;/a&gt;,
       +the Markdown renderer that &lt;a href="https://kokada.dev/blog/making-a-blog-for-the-next-10-years"&gt;powers this
       +blog&lt;/a&gt;, does not
       +have good examples or documentation (at least I didn't found neither).&lt;/p&gt;
       +&lt;p&gt;So I was not really hopeful I would get any answers in ChatGPT, but not only it
       +put me in the correct path, the code was almost correct. It did had some unused
       +and missing imports, and there were some compilation errors that were easily
       +fixed. And when it registered the extension, it also added a
       +&lt;code&gt;html.WithHardWraps()&lt;/code&gt; renderer for no reason at all.&lt;/p&gt;
       +&lt;p&gt;But this is one of the experiences in LLM that most impressed me. I had no idea
       +at all on how to solve this problem, and ChatGPT basically gave me the
       +solution. A &lt;a href="https://github.com/thiagokokada/blog/blob/3a2ea58a0638d1005617d25eabeae0626933db46/link_rewriter.go"&gt;modified version of the
       +code&lt;/a&gt;
       +from this chat is still used to power this blog to this day, that also
       +demonstrates that the code is not garbage (since now I understand what it does
       +and I can even extend it).&lt;/p&gt;
       +&lt;p&gt;I am still not sure how the license situation of this code is though. Does
       +licenses even make sense in AI generated code?&lt;/p&gt;
       +&lt;h2&gt;The bad&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/95fcd777-b03c-4cfe-8d1d-12e53f5a4dfe"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;I want to configure VM options inside IntelliJ by project&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;My project uses sbt, and I want to set this in a way that I can share with my
       +colleagues&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;I am using a build.sbt file, but IntelliJ is not loading it's JVM options and
       +I need to set this up manually. I want to automate this&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This is an example of my most common interaction with ChatGPT: it generally
       +gives some options that could work, but not in my specific case. So I start to
       +ask for more information until it eventually starts to
       +&lt;a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)"&gt;hallucinate&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;In this particular case, the hallucination was in:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;The sbt-idea-plugin can generate IntelliJ IDEA project files based on your
       +SBT configuration, including JVM options. This allows you to ensure that
       +IntelliJ loads the correct settings from the SBT configuration.&lt;/p&gt;
       +&lt;p&gt;Steps:&lt;/p&gt;
       +&lt;ol&gt;
       +&lt;li&gt;Add the &lt;code&gt;sbt-idea-plugin&lt;/code&gt; to your &lt;code&gt;project/plugins.sbt&lt;/code&gt; file:&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;addSbtPlugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;com.github.mwz&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;sbt-idea&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;2.4.0&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
       +&lt;li&gt;Run the following command in your terminal to regenerate IntelliJ IDEA
       +project files:&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sbt gen-idea
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
       +&lt;p&gt;Yes, there is a
       +&lt;a href="https://github.com/JetBrains/sbt-idea-plugin"&gt;&lt;code&gt;sbt-idea-plugin&lt;/code&gt;&lt;/a&gt;, but no, it
       +doesn't generate project files based in my SBT configuration. It is not even
       +from the repository that it is supposed to be pointing too.&lt;/p&gt;
       +&lt;p&gt;I will just not say that this interaction was a waste of time because some of
       +the other ideas can be applied, but this was the kind of case where I ended up
       +just being disappointed because the thing that seemed to be a possible solution
       +for my problem ended up not existing at all.&lt;/p&gt;
       +&lt;h2&gt;The ugly&lt;/h2&gt;
       +&lt;p&gt;There are lots of examples where ChatGPT just plain disappointed me. Here is a
       +few:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;What is the language that is a fork of Go that has extra features like map?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/2b854bbd-d376-4049-97fe-b47321c1ef07"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;This is one of those cases where I saw something once and was trying to
       +remember. ChatGPT just gave me lots of answers that were not a fork of Go
       +(&lt;a href="https://vlang.io/"&gt;V&lt;/a&gt;, &lt;a href="https://github.com/gluon-lang/gluon"&gt;Gluon&lt;/a&gt;,
       +&lt;a href="https://www.ponylang.io"&gt;Pony&lt;/a&gt;), hallucinated a programming language (Gomini),
       +and said that &lt;a href="https://ziglang.org/"&gt;Zig&lt;/a&gt; is focused in STEM.&lt;/p&gt;
       +&lt;p&gt;By the way, I eventually remember what the language was:
       +&lt;a href="https://goplus.org/"&gt;Go+&lt;/a&gt;. To be fair with ChatGPT, I misremembered that the
       +Go+ had map (as the &lt;a href="https://en.wikipedia.org/wiki/Map_(higher-order_function)"&gt;high-order
       +function&lt;/a&gt;, not a
       +hashmap that both Go and Go+ have). But still, Go+ explicit announces that it
       +is a language for STEM everywhere in their website, and even them ChatGPT
       +couldn't answer.&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;In IntelliJ, I need to pass some VM options to the run configuration for my
       +application to run tests, e.g.:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;--add-exports=java.base/sun.security.x509=ALL-UNNAMED
       +--add-opens=java.base/sun.security.ssl=ALL-UNNAMED
       +-Dconfig.resource=api.test.conf
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;However, the &lt;code&gt;api.test.conf&lt;/code&gt; may be &lt;code&gt;jobs.api.conf&lt;/code&gt; or something else
       +depending on the subproject I am running inside Idea. Can I parametrize this
       +in some way?&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;&lt;a href="https://chatgpt.com/share/aa9bd051-65de-49d9-8d56-0e28de94722c"&gt;Chat history&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;So quick answer: you can't. IntelliJ macros are not powerful enough for this.
       +ChatGPT itself said so when I asked about the IntelliJ macros
       +&lt;a href="https://chatgpt.com/share/7a057bf5-bcec-458b-9432-98e3f551ad28"&gt;directly&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;But in this particular case, ChatGPT happily said &amp;quot;yes, you can parameterize VM
       +options in IntelliJ IDEA to make them more flexible depending on the subproject
       +or other context&amp;quot;. Thanks for wasting my time, I guess.&lt;/p&gt;
       +&lt;h2&gt;Conclusion&lt;/h2&gt;
       +&lt;p&gt;I do think that LLMs, in particular ChatGPT since this is the one I have more
       +experience with, are improving. But still, if there is anything that I
       +concluded after putting all those experiences in a post is that you still need
       +someone that knows at least as much about whatever you're asking ChatGPT to
       +actually validate the answer.&lt;/p&gt;
       +&lt;p&gt;Not only because LLMs hallucinates a lot, but also because even in the &amp;quot;good&amp;quot;
       +example the LLM needed extra input to figure out about the possible drawback of
       +the solution. The only case where I felt that ChatGPT outsmarted me was the
       +&amp;quot;amazing&amp;quot; case, but this was literally the only example that I could find in
       +all my chat history with ChatGPT that was like this.&lt;/p&gt;
       +&lt;p&gt;So yes, at least for now I still don't think AI will be our overlords. Maybe
       +someday.&lt;/p&gt;</description><pubDate>Mon, 02 Sep 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/my-coding-experience-with-llm/</guid></item><item><title>Making a blog for the next 10 years</title><link>https://kokada.dev/blog/making-a-blog-for-the-next-10-years/</link><description>&lt;p&gt;So one thing that I realise after starting writing this blog is that I care
       +more about it than some of my other projects. For some reason or another I want
       +to make sure that this blog will continue with me for a long time. This is one
       +of the reasons why I use &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;GitHub as mirror
       +blog&lt;/a&gt; and why I
       +created a &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;bunch of
       +automation&lt;/a&gt;
       +to make sure I never forget how to maintain this blog.&lt;/p&gt;
       +&lt;p&gt;Still, there are a bunch of dependencies that I need to make sure they're
       +working so I can publish blog posts:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;Markdown&lt;/li&gt;
       +&lt;li&gt;A text editor&lt;/li&gt;
       +&lt;li&gt;Hosting and blog platform&lt;/li&gt;
       +&lt;li&gt;Shell script and
       +&lt;a href="https://kokada.dev/blog/abusing-makefiles-for-fun-and-profit"&gt;Makefile&lt;/a&gt;&lt;/li&gt;
       +&lt;li&gt;Go toolchain and modules&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;Let's start with the most important one: the texts themselves, they're all
       +written in &lt;a href="https://www.markdownguide.org/"&gt;Markdown&lt;/a&gt;. The nice thing about
       +Markdown is that it is just plain text files with some special notation that
       +allow you to format text, but the text itself is still legible even if you have
       +no support to render Markdowns. So it means as long as I can read a plain text
       +file, I can still read the posts, no issue there. Closely related issue here is
       +the text editor, but the nice thing about Markdown is that if tomorrow I decide
       +to change from &lt;code&gt;neovim&lt;/code&gt; to, say, &lt;a href="https://zed.dev/"&gt;Zed&lt;/a&gt;, I can still write it
       +Markdown files without issue. I also use a standardised Markdown implementation
       +called &lt;a href="https://commonmark.org/"&gt;CommonMark&lt;/a&gt;, that fixes a bunch of ambiguities
       +of the original Markdown spec.&lt;/p&gt;
       +&lt;p&gt;The next point is the host (&lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt;) and the blog
       +platform (&lt;a href="https://github.com/mataroa-blog/mataroa"&gt;Mataroa&lt;/a&gt;). One of the nice
       +things about Mataroa is that it avoids platform lock-in by having multiple ways
       +to &lt;a href="https://mataroa.blog/export/"&gt;export your posts&lt;/a&gt;. I could export everything
       +to &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;, for example, and serve the blog from &lt;a href="https://pages.github.com/"&gt;GitHub
       +Pages&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;This is especially nice consider how small &lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt; is,
       +so it is good to know that if I eventually have issues I could move to
       +somewhere else. I also have an e-mail backup every month with all posts made
       +(another &lt;a href="https://hey.mataroa.blog/blog/monthly-auto-exports-via-email/"&gt;Mataroa
       +feature&lt;/a&gt;), and
       +of course I have a &lt;a href="https://github.com/thiagokokada/blog"&gt;Git repo&lt;/a&gt; that also
       +acts as a &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;mirror of this
       +blog&lt;/a&gt;. So I would
       +say the chance of losing access to the content is slim.&lt;/p&gt;
       +&lt;p&gt;One other issue is the URL of the posts that are indexed in many different
       +places, like &lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;,
       +&lt;a href="https://lobste.rs/"&gt;Lobte.rs&lt;/a&gt;, etc. This is why I finally decide to bite the
       +bullet and purchase a proper domain, and this website should now be available
       +in &lt;a href="kokada.dev"&gt;kokada.dev&lt;/a&gt;. This means that in my eventual exit from
       +&lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt;, I can just point my new blog location to my own
       +domain if needed (it is not as easy since I also need to preserve the post
       +URLs, but shouldn't be difficult to fix this if I ever need to do so).&lt;/p&gt;
       +&lt;p&gt;Now for the tools that I use to publish from the original Markdown files to
       +everything else. Let's start with shell script(s) and Makefile: I decided that
       +they're less of an issue if they eventually stop working: they're only used to
       +make my life easier, but I can still publish files manually if needed. Still, I
       +tried to rewrite both the
       +&lt;a href="https://github.com/thiagokokada/blog/commit/a0d421ca90f3da059998295c5e3c6c7a6a3f0688"&gt;shell&lt;/a&gt;
       +and
       +&lt;a href="https://github.com/thiagokokada/blog/commit/074580065b21fbdaf930aa51968e69f015d49505"&gt;Makefile&lt;/a&gt;
       +to avoid GNUisms, so in the eventual case that I decide to e.g.: stop using a
       +GNU/Linux system like NixOS and use a *BSD system instead, I am covered.&lt;/p&gt;
       +&lt;p&gt;In other projects I would probably add a &lt;code&gt;shell.nix&lt;/code&gt; file and add the GNU
       +versions of the tools that I am using, but I am trying to avoid Nix while
       +building this blog as much as I can, mainly because it is not available
       +everywhere (e.g.: *BSD systems).&lt;/p&gt;
       +&lt;p&gt;Go is the more important part: the tooling used to &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;publish this blog is
       +written in
       +Go&lt;/a&gt;. Go
       +is a good language when you want to ensure that things will work for a long
       +time because of its &lt;a href="https://go.dev/blog/compat"&gt;backwards compatibility
       +guarantee&lt;/a&gt;. Also I don't expect Google dropping Go
       +development soon, but even if this happen (&lt;a href="https://killedbygoogle.com/"&gt;&amp;quot;killed by
       +Google&amp;quot;&lt;/a&gt; is a thing after all), it is very likely
       +some other group or company would adopt its development quickly, considering
       +&lt;a href="https://www.tiobe.com/tiobe-index/go/"&gt;how popular the language&lt;/a&gt; is.&lt;/p&gt;
       +&lt;p&gt;However, the &lt;a href="https://github.com/thiagokokada/blog/blob/main/go.mod"&gt;Go
       +modules&lt;/a&gt; that I depend
       +are another story:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;&lt;a href="https://github.com/elliotchance/orderedmap/"&gt;elliotchance/orderedmap&lt;/a&gt;: an
       +ordered map implementation that I use until Go adds it in the &lt;a href="https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go"&gt;standard
       +library&lt;/a&gt;&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/gorilla/feeds"&gt;gorilla/feeds&lt;/a&gt;: a RSS generator library&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/gosimple/slug"&gt;gosimple/slug&lt;/a&gt;: a
       +&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Slug"&gt;slug&lt;/a&gt; generator
       +library&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/yuin/goldmark"&gt;yuin/goldmark&lt;/a&gt;: a CommonMark parser and
       +renderer&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://github.com/teekennedy/goldmark-markdown"&gt;teekennedy/goldmark-markdown&lt;/a&gt;:
       +a renderer for Goldmark to render back to Markdown (since Goldmark itself
       +doesn't have this capacity)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;In common for all those modules are that they're clearly small projects
       +maintained mostly by one developer. They're all very good, don't get me wrong,
       +but they're still an reliability issue in the future. There is no guarantee
       +those repositories will not be deleted tomorrow, for example.&lt;/p&gt;
       +&lt;p&gt;Yes, &lt;a href="https://proxy.golang.org/"&gt;Go Proxy&lt;/a&gt; exist, but from what I understood
       +reading its page is that while it caches modules contents, this is not
       +guarantee:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;proxy.golang.org does not save all modules forever. There are a number of
       +reasons for this, but one reason is if proxy.golang.org is not able to detect
       +a suitable license. In this case, only a temporarily cached copy of the
       +module will be made available, and may become unavailable if it is removed
       +from the original source and becomes outdated. The checksums will still
       +remain in the checksum database regardless of whether or not they have become
       +unavailable in the mirror.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;This is why this is the first project that made sense to me to use &lt;a href="https://go.dev/ref/mod#go-mod-vendor"&gt;&lt;code&gt;go mod vendor&lt;/code&gt;&lt;/a&gt;. Now I have a copy of the source
       +code of all modules inside the
       +&lt;a href="https://github.com/thiagokokada/blog/tree/0b97630d6b30551ffe05b5d8124305b1065f729d/vendor"&gt;vendor&lt;/a&gt;
       +directory in the repository, avoiding the risks I commented above. This allows
       +me to ensure that this blog will still be publishable in the future, as long as
       +I have a working Go toolchain (and Go toolchain makes this
       +&lt;a href="https://kokada.dev/blog/quick-bits-go-automatically-downloads-a-newer-toolchain-if-needed"&gt;easy&lt;/a&gt;).&lt;/p&gt;
       +&lt;p&gt;There are a few other things that can bitrot this blog, for example links going
       +nowhere. I always try to use
       +&lt;a href="https://en.wikipedia.org/wiki/Permalink"&gt;permalinks&lt;/a&gt; when it makes sense, but
       +the only actual way to ensure those links would work in the future would be to
       +point them to &lt;a href="https://archive.org/"&gt;archive.org&lt;/a&gt; (but even archive.org may not
       +exist forever). Maybe something to fix in the future, hope not in the far
       +future, before things start to break.&lt;/p&gt;</description><pubDate>Sat, 24 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/making-a-blog-for-the-next-10-years/</guid></item><item><title>An unordered list of hidden gems inside NixOS</title><link>https://kokada.dev/blog/an-unordered-list-of-hidden-gems-inside-nixos/</link><description>&lt;p&gt;After using &lt;a href="https://nixos.org/"&gt;NixOS&lt;/a&gt; for the last 5+ years as my main OS, I
       +end up with a &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;configuration&lt;/a&gt;
       +with many things that are interesting for one reason or another, but it is not
       +listed anywhere (well, except if you are the kind of person that reads &lt;code&gt;man 5 configuration.nix&lt;/code&gt; or the release notes in every release).&lt;/p&gt;
       +&lt;p&gt;So kind in the same spirit as my &lt;a href="https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go"&gt;list of things that I miss in
       +Go&lt;/a&gt;, here is
       +a list of modules that I find neat in NixOS and are not default already. Again,
       +the list is unordered since this makes it easier to update in the future if I
       +find something else, but also I don't want to think too hard about an order
       +here.&lt;/p&gt;
       +&lt;p&gt;With all above, let's start.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/6afb255d976f85f3359e4929abd6f5149c323a02/nixos/modules/services/networking/nftables.nix"&gt;&lt;code&gt;networking.nftables&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://www.nftables.org/"&gt;nftables&lt;/a&gt; is, accordingly to Wikipedia:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;nftables is a subsystem of the Linux kernel providing filtering and
       +classification of network packets/datagrams/frames.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;It is basically a replacement of the venerable
       +&lt;a href="https://en.wikipedia.org/wiki/Iptables"&gt;iptables&lt;/a&gt;, that still exist and is the
       +default program to configure the famous &lt;code&gt;networking.firewall&lt;/code&gt;, the declarative
       +&lt;a href="https://wiki.nixos.org/wiki/Firewall"&gt;Firewall&lt;/a&gt; that NixOS enable by default.&lt;/p&gt;
       +&lt;p&gt;To enable, it is simple, just add to your configuration:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;networking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nftables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And thanks to the fact that NixOS's Firewall is declarative, everything should
       +still work as expect: any service that you set &lt;code&gt;openFirewall = true&lt;/code&gt; will still
       +have its ports open, if you set &lt;code&gt;networking.firewall.allowPing = false&lt;/code&gt; it will
       +still disable pings like before, etc.&lt;/p&gt;
       +&lt;p&gt;If you look at the documentation of the above option, you will find the
       +following warning:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Note that if you have Docker enabled you will not be able to use nftables
       +without intervention. Docker uses iptables internally to setup NAT for
       +containers. This module disables the ip_tables kernel module, however Docker
       +automatically loads the module. Please see
       +&lt;a href="https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273"&gt;https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273&lt;/a&gt; for
       +more information.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;I don't use Docker (switched to Podman instead for quite a long time), so I
       +don't know how bad the situation is. Also keep in mind that &lt;code&gt;nftables&lt;/code&gt; does
       +offer &lt;code&gt;iptables-compat&lt;/code&gt; for compatibility with old iptables scripts, so it is
       +most likely Docker doing something weird here.&lt;/p&gt;
       +&lt;p&gt;Now, the actual advantage from the user here is not clear: the main advantage
       +from my point of view (and the reason I used to use in other distros like Arch)
       +is the improved syntax, however if you are using the declarative NixOS's
       +Firewall you are not interacting with either &lt;code&gt;iptables&lt;/code&gt; or &lt;code&gt;nftables&lt;/code&gt; directly
       +anyway. &lt;code&gt;nftables&lt;/code&gt; is supposed to be more efficient, but not sure most users
       +will care about this.&lt;/p&gt;
       +&lt;p&gt;However if you are the kind of person that needs custom rules, switching to
       +&lt;code&gt;nftables&lt;/code&gt; does bring a few benefits, including
       +&lt;code&gt;networking.nftables.checkRuleset&lt;/code&gt; (enabled by default), that checks if your
       +ruleset has syntax errors during build time. Really valuable to avoid issues
       +only after switch.&lt;/p&gt;
       +&lt;p&gt;Anyway, this is one of those options that I think it should be the default for
       +a long time, since most of the new development in NixOS firewall seems to be
       +focusing &lt;code&gt;nftables&lt;/code&gt; for a while.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/877d19523edcac81b167e8fd716ad2658da2adca/nixos/modules/system/activation/switchable-system.nix#L30-L38"&gt;&lt;code&gt;system.switch.enableNg&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;a href="https://github.com/NixOS/nixpkgs/pull/308801"&gt;This one&lt;/a&gt; I just discovered
       +today, but it has been available for a while (~2 months if you're using
       +&lt;code&gt;nixos-unstable&lt;/code&gt;). Finally someone is rewriting
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/b1eff03c35aa7c90ab3a4d9f6ef297dae5fba37b/nixos/modules/system/activation/switch-to-configuration.pl"&gt;&lt;code&gt;switch-to-configuration.pl&lt;/code&gt;&lt;/a&gt;,
       +the Perl script that is called everytime you run &lt;code&gt;nixos-rebuild switch&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;Now, I am not one of those &amp;quot;rewrite in Rust&amp;quot; zealots, but in this case this is
       +definitely worth it: &lt;code&gt;switch-to-configuration.pl&lt;/code&gt; is one of those pieces of
       +code in NixOS that most people avoid touching at the fear of breaking
       +something. There is a reason why
       +&lt;a href="https://github.com/NixOS/nixpkgs/commit/eeb2588a59c938042b74183ce1da7052a6ef7e59"&gt;&lt;code&gt;nixos-rebuild&lt;/code&gt;&lt;/a&gt;
       +is as convoluted as it is, because even if it is a messy shell script, most
       +people preferred to workaround issues from the &lt;code&gt;switch-to-configuration.pl&lt;/code&gt;
       +inside it than trying to understand the mess that &lt;code&gt;switch-to-configuration.pl&lt;/code&gt;
       +is.&lt;/p&gt;
       +&lt;p&gt;Trying this one is easy:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enableNg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yes, you need to explicit set &lt;code&gt;system.switch.enable = false&lt;/code&gt;, since the default
       +is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;By the way, what is the reason you would want to set &lt;code&gt;system.switch.enable = false&lt;/code&gt; before the &lt;code&gt;enableNg&lt;/code&gt; appeared you ask? For systems that are immutable
       +and updated by e.g.: image upgrades instead of modifying root.&lt;/p&gt;
       +&lt;p&gt;Enabling &lt;code&gt;switch-to-configuration-ng&lt;/code&gt; right now is mostly for testing purposes,
       +but one of the advantages that I saw is that system switches are (slightly)
       +faster:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;sudo nixos-rebuild switch&amp;quot; # switch-to-configuration.pl
       +Benchmark 1: sudo nixos-rebuild switch
       +  Time (mean ± σ):      3.576 s ±  0.035 s    [User: 0.004 s, System: 0.014 s]
       +  Range (min … max):    3.522 s …  3.645 s    10 runs
       +&lt;/code&gt;&lt;p&gt;&lt;code&gt;$ hyperfine &amp;quot;sudo nixos-rebuild switch&amp;quot; # switch-to-configuration-ng
       +Benchmark 1: sudo nixos-rebuild switch
       +Time (mean ± σ):      3.394 s ±  0.080 s    [User: 0.004 s, System: 0.013 s]
       +Range (min … max):    3.325 s …  3.608 s    10 runs
       +&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
       +&lt;p&gt;But yes, the difference is not enough to make a significant impact, and it is
       +not the objective anyway. The real reason for the rewrite is to make it easier
       +to collaborate. I hope one day we also have someone brave enough to rewrite the
       +&lt;code&gt;nixos-rebuild&lt;/code&gt; script in something saner.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/cce9aef6fd8f010d288d685b9d2a38f3b6ac47e9/nixos/modules/system/boot/systemd/initrd.nix"&gt;&lt;code&gt;boot.initrd.systemd&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;A quick recap on how a modern Linux distro generally boots: the first thing
       +that the bootloader (say &lt;a href="https://www.gnu.org/software/grub/"&gt;GRUB&lt;/a&gt; or
       +&lt;a href="https://systemd.io/BOOT/"&gt;systemd-boot&lt;/a&gt;) loads is &lt;code&gt;initrd&lt;/code&gt; (&lt;em&gt;initial
       +ramdisk&lt;/em&gt;), a small image that runs from RAM and includes the Linux kernel and
       +some utilities that are responsible for setting up the main system. For
       +example, one of the responsibilities of the &lt;code&gt;initrd&lt;/code&gt; is to mount the disks and
       +start init system (&lt;code&gt;systemd&lt;/code&gt;).&lt;/p&gt;
       +&lt;p&gt;It may surprising that this &lt;code&gt;initrd&lt;/code&gt; image does &lt;strong&gt;not&lt;/strong&gt; generally include
       +&lt;code&gt;systemd&lt;/code&gt;. Traditionally &lt;code&gt;initrd&lt;/code&gt; is composed by a bunch of shell scripts and a
       +minimal runtime (e.g.: &lt;a href="https://www.busybox.net/"&gt;busybox&lt;/a&gt;), however &lt;code&gt;systemd&lt;/code&gt;
       +can also do this job since a long time ago. It is just the role of the distros
       +to integrate &lt;code&gt;systemd&lt;/code&gt; inside the &lt;code&gt;initrd&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;This is what &lt;code&gt;boot.initrd.systemd&lt;/code&gt; does: enable &lt;code&gt;systemd&lt;/code&gt; inside the &lt;code&gt;initrd&lt;/code&gt;.
       +It make a few subtle changes:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;If you are using &lt;a href="https://wiki.nixos.org/wiki/Full_Disk_Encryption"&gt;Full Disk Encryption via
       +LUKS&lt;/a&gt;, you will get a
       +different password prompt at login&lt;/li&gt;
       +&lt;li&gt;You will get &lt;code&gt;initrd&lt;/code&gt; time information if using &lt;code&gt;systemd-analyze&lt;/code&gt; to measure
       +boot time
       +&lt;ul&gt;
       +&lt;li&gt;You can get even more information (bootloader) if you also use
       +&lt;code&gt;systemd-boot&lt;/code&gt;&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;You will also get &lt;code&gt;systemd&lt;/code&gt; style status about services during &lt;code&gt;initrd&lt;/code&gt; (not
       +only afterwards)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;But I think the main reason is that since &lt;code&gt;systemd&lt;/code&gt; is event-driven, it should
       +make boot more reliable, especially in challenging situations (like booting
       +from network). I can't say that I have any system like this to test if it is
       +actually more reliable or not, but I don't remember having any issues since I
       +set &lt;code&gt;boot.initrd.systemd.enable = true&lt;/code&gt;, so there is that.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/b4a09f1f9d1599478afadffa782a02690550447c/pkgs/development/libraries/pipewire/default.nix"&gt;&lt;code&gt;services.pipewire&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;If there is something in that list that has a good chance that you're using
       +already, it is this one, especially if you're using
       +&lt;a href="https://wayland.freedesktop.org/"&gt;Wayland&lt;/a&gt;. Still, I think it is interesting
       +to include in this list since &lt;a href="https://www.pipewire.org/"&gt;PipeWire&lt;/a&gt; is great.&lt;/p&gt;
       +&lt;p&gt;The experience with PipeWire until now for me was seamless: I never had any
       +issues with it, all my applications still work exactly as it always worked. I
       +also didn't had any issues with
       +&lt;a href="https://www.freedesktop.org/wiki/Software/PulseAudio/"&gt;PulseAudio&lt;/a&gt; for a
       +while, but I still remember when I first tried PulseAudio during the 0.x in
       +Fedora and having tons of issues. So bonus points for PipeWire developers for
       +polishing the experience of enough that most people will feel no difference.&lt;/p&gt;
       +&lt;p&gt;To enable PipeWire, I would recommend:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pipewire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;alsa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pulse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# jack.enable = true;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This enables both ALSA and PulseAudio emulation support in PipeWire for maximum
       +compatibility with desktop applications (you can also enable
       +&lt;a href="https://jackaudio.org/"&gt;&lt;code&gt;jack&lt;/code&gt;&lt;/a&gt; if you use professional audio applications).
       +It also enables &lt;a href="https://github.com/heftig/rtkit"&gt;&lt;code&gt;rtkit&lt;/code&gt;&lt;/a&gt;, allowing PipeWire
       +to get (soft) realtime, helping avoiding cracks during high CPU load.&lt;/p&gt;
       +&lt;p&gt;I also recommend taking a look at the &lt;a href="https://wiki.nixos.org/wiki/PipeWire"&gt;Wiki
       +article&lt;/a&gt;, that has multiple interesting
       +configurations that can be added for low-latency setups or improved codecs for
       +Bluetooth devices.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/c9ec8289781a3c4ac4dd5c42c8d50dd65360e79c/nixos/modules/services/networking/networkmanager.nix#L264-L271"&gt;&lt;code&gt;networking.networkmanager.wifi.backend = &amp;quot;iwd&amp;quot;&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;There is a good change that you're using
       +&lt;a href="https://www.networkmanager.dev/"&gt;&lt;code&gt;NetworkManager&lt;/code&gt;&lt;/a&gt; to manage network,
       +especially for Wi-Fi. And if that is the case, I can't recommend enough
       +changing the backend from the default &lt;code&gt;wpa_supplicant&lt;/code&gt; to
       +&lt;a href="https://iwd.wiki.kernel.org/"&gt;&lt;code&gt;iwd&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;If you think that your Wi-Fi takes a long time to connect/re-connect, it may be
       +because &lt;code&gt;wpa_supplicant&lt;/code&gt;. &lt;code&gt;iwd&lt;/code&gt; seems much more optimised in this regard, and
       +since switching to it I never felt that my Wi-Fi was worse than other OSes (and
       +generally slightly better than Windows, but keep in mind that this is a
       +non-scientific comparison).&lt;/p&gt;
       +&lt;p&gt;Not saying that I never had Wi-Fi issues since switching to &lt;code&gt;iwd&lt;/code&gt;, however
       +switching back to &lt;code&gt;wpa_supplicant&lt;/code&gt; in those cases never fixed the issue (it was
       +the same or worse), so I assume either bad hardware or drivers in those cases.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/d5badef1e0416160298aad6dd8604eaf8b6e7e52/nixos/modules/system/boot/tmp.nix#L25-L32"&gt;&lt;code&gt;boot.tmp.useTmpfs&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Most Linux distro nowadays mount &lt;code&gt;/tmp&lt;/code&gt; inside
       +&lt;a href="https://en.wikipedia.org/wiki/Tmpfs"&gt;tmpfs&lt;/a&gt;, storing the files inside RAM (and
       +making them actually temporary, e.g.: does not persist between reboots). There
       +is a &lt;a href="https://lwn.net/Articles/499410/"&gt;long discussion&lt;/a&gt; whether this makes
       +sense or not, but it is a fact that using &lt;code&gt;/tmp&lt;/code&gt; as an storage for small files
       +generally makes sense.&lt;/p&gt;
       +&lt;p&gt;However, NixOS still stores &lt;code&gt;/tmp&lt;/code&gt; inside &lt;code&gt;/&lt;/code&gt; by default. The main reason for
       +this is because Nix, by default, still builds everything in &lt;code&gt;TMPDIR&lt;/code&gt; (that
       +defaults to &lt;code&gt;/tmp&lt;/code&gt; if not set). To fix the situation you can do:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useTmpfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nix-daemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TMPDIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/var/tmp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This ensures that Nix will build in &lt;code&gt;/var/tmp&lt;/code&gt;, that is the temporary directory
       +for large files.&lt;/p&gt;
       +&lt;p&gt;If you don't want to use &lt;code&gt;/tmp&lt;/code&gt; inside tmpfs for some reason, I recommend at
       +least setting &lt;code&gt;boot.tmp.cleanOnBoot = true&lt;/code&gt;. This is to avoid issues with e.g.:
       +mistakenly writing a program that depends in &lt;code&gt;/tmp&lt;/code&gt; being persistent, because
       +this is the behavior that most other distros expects.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/68142254d20256e617f596a5d50f9950f98fa36a/nixos/modules/config/zram.nix"&gt;&lt;code&gt;zramSwap&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Have a system with small amounts of RAM? &lt;del&gt;You can download more RAM for free,
       +just click this &lt;a href="https://downloadmoreram.com/"&gt;link&lt;/a&gt;.&lt;/del&gt; Well, just kidding, but
       +you can actually improve RAM usage by compressing it. This is what
       +&lt;a href="https://en.wikipedia.org/wiki/Zram"&gt;&lt;code&gt;zram&lt;/code&gt;&lt;/a&gt; does: it will create a block
       +device using part of your RAM that will be compressed.&lt;/p&gt;
       +&lt;p&gt;While this means that whatever is in the compressed part can't be used as-is
       +(it needs to be decompressed first), it means you can store a lot more
       +information. And since RAM is much faster than disk, this generally is a good
       +trade-off. It is still recommended that you have some swap space though, e.g.:
       +to swap pages that are unused.&lt;/p&gt;
       +&lt;p&gt;To enable it, you can do:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;zramSwap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;algorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zstd&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can play a little with the &lt;code&gt;zramSwap.algorithm&lt;/code&gt; parameter, but I recommend
       +either &lt;code&gt;lz4&lt;/code&gt; or &lt;code&gt;zstd&lt;/code&gt;. It is important for the compression algorithm to be
       +fast, since you probably want to minimise CPU usage. Well, unless you have a
       +very good CPU and small amounts of RAM, them it may make sense to use an
       +algorithm that compress better but uses more CPU.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/5356420466c4d7901b63acc5e337c5bf30573f8a/nixos/modules/services/misc/fstrim.nix"&gt;&lt;code&gt;services.fstrim&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Do you have a SSD? If so, you probably want to
       +&lt;a href="https://en.wikipedia.org/wiki/Trim_(computing)"&gt;trim&lt;/a&gt; it regularly to keep a
       +good performance. This should be as easy as:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fstrim&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One caveat though: if you are using Full Disk Encryption with LUKS, you also
       +need to enable &lt;code&gt;boot.initrd.luks.devices.&amp;lt;name&amp;gt;.allowDiscards&lt;/code&gt;, otherwise
       +&lt;code&gt;fstrim&lt;/code&gt; will not work. The reason that this is not enabled by default is
       +because there are some &lt;a href="https://asalor.blogspot.com/2011/08/trim-dm-crypt-problems.html"&gt;security
       +implications&lt;/a&gt;.
       +It shouldn't be an issue for most people, but if you are paranoid please assess
       +your risks first.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/e83fc891118e5e5660ccca000040e33b0a0a3080/nixos/modules/system/boot/binfmt.nix"&gt;&lt;code&gt;boot.binfmt.emulatedSystems&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;This is one of my favorites. Do you want to have the power to run binaries from
       +other architectures like
       +&lt;a href="https://en.wikipedia.org/wiki/ARM_architecture_family"&gt;ARM&lt;/a&gt; or
       +&lt;a href="https://en.wikipedia.org/wiki/RISC-V"&gt;RISC-V&lt;/a&gt;? In NixOS it is easy:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binfmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emulatedSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aarch64-linux&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;riscv64-linux&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will install a QEMU emulator for the target architectures, and configure
       +&lt;a href="https://en.wikipedia.org/wiki/Binfmt_misc"&gt;&lt;code&gt;binfmt&lt;/code&gt;&lt;/a&gt; so that the kernel will
       +automatically detect when a binary from another architecture is run,
       +running it with the QEMU emulator instead.&lt;/p&gt;
       +&lt;p&gt;Now, keep in mind that you still need e.g.: libraries for the target
       +architecture. However this is where the Nix magic comes in, you can easily
       +compile something to another architecture as:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; uname -a
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Linux sankyuu-nixos 6.10.3 #1-NixOS SMP PREEMPT_DYNAMIC Sat Aug  3 07:01:09 UTC 2024 x86_64 GNU/Linux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; nixpkgs
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#hello --system aarch64-linux
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; file ./result/bin/hello
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./result/bin/hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /nix/store/kz7xglxzhad64v667wwpn8vrxhjwcbna-glibc-2.39-52/lib/ld-linux-aarch64.so.1, for GNU/Linux 3.10.0, not stripped
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./result/bin/hello
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, world!
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And since the binary will be linked with the libraries from the target
       +architecture, everything works as expect.&lt;/p&gt;
       +&lt;p&gt;You also need to temper your expectations: QEMU emulation is slow. It is
       +sufficient fast to build small programs, but if you need to build something
       +bigger, I would recommend you to do it via &lt;a href="https://kokada.dev/blog/building-static-binaries-in-nix"&gt;cross-compilation
       +instead&lt;/a&gt;.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/d9cf6ea6f809b862b73766705bdadb1236eeeff8/nixos/modules/services/system/dbus.nix#L40-L50"&gt;&lt;code&gt;services.dbus.implementation = &amp;quot;broker&amp;quot;&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Someone in
       +&lt;a href="https://lobste.rs/s/ymmale/unordered_list_hidden_gems_inside_nixos#c_dz1hck"&gt;Lobste.rs&lt;/a&gt;
       +remembered me of this gem: &lt;a href="https://github.com/bus1/dbus-broker"&gt;dbus-broker&lt;/a&gt;,
       +a high performance implementation of
       +&lt;a href="https://en.wikipedia.org/wiki/D-Bus"&gt;D-Bus&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;For those who doesn't know, D-Bus is the &lt;a href="https://en.wikipedia.org/wiki/Inter-process_communication"&gt;Inter-Process
       +Communication&lt;/a&gt;
       +protocol used mostly in Linux desktop. And distros like
       +&lt;a href="https://archlinux.org/news/making-dbus-broker-our-default-d-bus-daemon/"&gt;Arch&lt;/a&gt;
       +and
       +&lt;a href="https://fedoraproject.org/wiki/Changes/DbusBrokerAsTheDefaultDbusImplementation"&gt;Fedora&lt;/a&gt;
       +already use it to default, so there aren't lots of reason to not use it.&lt;/p&gt;
       +&lt;h2&gt;&lt;a href="https://github.com/NixOS/nixpkgs/blob/6afb255d976f85f3359e4929abd6f5149c323a02/nixos/modules/services/hardware/irqbalance.nix"&gt;&lt;code&gt;services.irqbalance&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-23&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Here is one that I have no experience but got curious after this comment in
       +&lt;a href="https://lobste.rs/s/ymmale/unordered_list_hidden_gems_inside_nixos#c_aghpwy"&gt;Lobte.rs&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;One more option to add is maybe &lt;code&gt;services.irqbalance.enable&lt;/code&gt;. For my old
       +laptop it’s a life savior: no more freezes of the DE or video playback during
       +heavy operations like e.g. compilation.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Never tried it before but will give a try!&lt;/p&gt;</description><pubDate>Wed, 21 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/an-unordered-list-of-hidden-gems-inside-nixos/</guid></item><item><title>Quick bits: using forks with Go modules</title><link>https://kokada.dev/blog/quick-bits-using-forks-with-go-modules/</link><description>&lt;p&gt;There are 2 types of blog posts: the ones you write for others, and the ones
       +that you write for yourself. This blog post is the latter kind. What I am going
       +to talk here is probably something most people know, but I didn't, and the
       +documentation is all scattered in the internet. So I am writing mostly to
       +remember myself, in future, if I have the same kind of issue what I need to do.&lt;/p&gt;
       +&lt;p&gt;The context: &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt;, the blog platform
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; is hosted on, relies in
       +&lt;a href="https://python-markdown.github.io/"&gt;python-markdown&lt;/a&gt;, predates
       +&lt;a href="https://commonmark.org/"&gt;CommonMark&lt;/a&gt; specification and as such, has some
       +incompatibilities. One of those incompatibilities with CommonMark is the way
       +sub lists are handled. From the documentation:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;The syntax rules clearly state that when a list item consists of multiple
       +paragraphs, “each subsequent paragraph in a list item must be indented by
       +either 4 spaces or one tab” (emphasis added). However, many implementations
       +do not enforce this rule and allow less than 4 spaces of indentation. The
       +implementers of Python-Markdown consider it a bug to not enforce this rule.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;CommonMark &lt;a href="https://spec.commonmark.org/0.31.2/#lists"&gt;relax those
       +restrictions&lt;/a&gt;, allowing a sublist to
       +be defined with just 2 spaces of indentation.&lt;/p&gt;
       +&lt;p&gt;So I have automated all posts from this blog &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;using
       +Go&lt;/a&gt; and
       +a CommonMark renderer called &lt;a href="https://github.com/yuin/goldmark/"&gt;Goldmark&lt;/a&gt;. I
       +them re-render the Markdown to Mataroa using a &lt;a href="https://github.com/teekennedy/goldmark-markdown"&gt;Markdown
       +renderer&lt;/a&gt; before publising to
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt;, because this allow me to do some
       +transformations in the original Markdown. It mostly works fine except for sub
       +lists, thanks to the fact that the Markdown renderer I am using renders sub
       +lists with 2 spaces.&lt;/p&gt;
       +&lt;p&gt;The only reason sub lists are working right now is because
       +&lt;a href="https://gluer.org/"&gt;@ratsclub&lt;/a&gt; fixed this issue in the fork that
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; runs. But I want to be compatible with
       +the official instance if I ever need to migrate.&lt;/p&gt;
       +&lt;p&gt;The solution? Let's fix this in a
       +&lt;a href="https://github.com/teekennedy/goldmark-markdown/pull/21"&gt;PR&lt;/a&gt;. However now that
       +I have code to fix the issue, how can I use it without waiting upstream to
       +merge my code?&lt;/p&gt;
       +&lt;p&gt;If you are using Go modules it is easy, you just need to use the &lt;a href="https://go.dev/ref/mod#go-mod-file-replace"&gt;&lt;code&gt;replace&lt;/code&gt;
       +directive&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;thiagokokada&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="mf"&gt;1.23&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;elliotchance&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;orderedmap&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt; &lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="mf"&gt;.4.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gorilla&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;feeds&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.2.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gosimple&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.14.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;teekennedy&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.3.0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;yuin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.7.4&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;yuin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;highlighting&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.0.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20220208100518&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;594&lt;/span&gt;&lt;span class="nx"&gt;be1970594&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;alecthomas&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;chroma&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.10.0&lt;/span&gt; &lt;span class="c1"&gt;// indirect
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dlclark&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;regexp2&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.11.4&lt;/span&gt; &lt;span class="c1"&gt;// indirect
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gosimple&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;unidecode&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.0.1&lt;/span&gt; &lt;span class="c1"&gt;// indirect
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;replace&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;teekennedy&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;thiagokokada&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;goldmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.0.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20240820111219&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f30775d8ed15&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will replace all usages of &lt;code&gt;github.com/teekennedy/goldmark-markdown&lt;/code&gt; to my
       +fork in &lt;code&gt;github.com/thiagokokada/goldmark-markdown&lt;/code&gt;. You even get all the
       +reproducibility of modules since Go automatically pins the commit.&lt;/p&gt;
       +&lt;p&gt;Since the Go format for versions is quite unique, you can just set to the
       +desired branch (e.g.: instead of &lt;code&gt;v0.0.0-20240820111219-f30775d8ed15&lt;/code&gt;, you can
       +use &lt;code&gt;add-sublist-length-opt&lt;/code&gt; that is the branch name) and run &lt;code&gt;go mod tidy&lt;/code&gt; to
       +fix the format.&lt;/p&gt;</description><pubDate>Tue, 20 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-using-forks-with-go-modules/</guid></item><item><title>Quick bits: Go automatically downloads a newer toolchain if needed</title><link>https://kokada.dev/blog/quick-bits-go-automatically-downloads-a-newer-toolchain-if-needed/</link><description>&lt;p&gt;I am using
       +&lt;a href="https://github.com/elliotchance/orderedmap/"&gt;elliotchance/orderedmap&lt;/a&gt; as my
       +choice of ordered maps (since Go &lt;a href="https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go"&gt;doesn't have
       +one&lt;/a&gt; in
       +standard library yet). I recently did a
       +&lt;a href="https://github.com/elliotchance/orderedmap/pull/41"&gt;PR&lt;/a&gt; to implement Go 1.23
       +iterators, because I find them neat, however I was avoiding to use it in the
       +code that generates this &lt;a href="https://github.com/thiagokokada/blog"&gt;blog&lt;/a&gt; since Go
       +1.23 was just released and is still not the default Go in
       +&lt;a href="https://github.com/NixOS/nixpkgs"&gt;nixpkgs&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;I decided that I would create a
       +&lt;a href="https://github.com/thiagokokada/blog/pull/2"&gt;branch&lt;/a&gt; and leave there for a few
       +months, until I decided to try to run the code locally and got this:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go build
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go: downloading go1.23.0 (darwin/arm64)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice. And before you ask, yes, the compiled binary works perfectly:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog &amp;gt; README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog -rss &amp;gt; rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So how does this work? Take a look at the documentation in the official &lt;a href="https://tip.golang.org/doc/toolchain"&gt;Golang
       +page&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Starting in Go 1.21, the Go distribution consists of a go command and a
       +bundled Go toolchain, which is the standard library as well as the compiler,
       +assembler, and other tools. The go command can use its bundled Go toolchain
       +as well as other versions that it finds in the local PATH or downloads as
       +needed.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;There are a bunch of rules here that I am not going to enter in detail (I
       +recommend you to read the official documentation), but a quick summary:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;
       +&lt;p&gt;Go will download a toolchain when either &lt;code&gt;go&lt;/code&gt; or &lt;code&gt;toolchain&lt;/code&gt; lines &lt;code&gt;go.mod&lt;/code&gt;
       +is set to a Go version higher than your current &lt;code&gt;go&lt;/code&gt; binary&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;But only if your &lt;code&gt;go&lt;/code&gt; binary is at least version 1.21, since this is the
       +version that introduces this behavior&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;
       +&lt;p&gt;You can force a specific toolchain with &lt;code&gt;GOTOOLCHAIN&lt;/code&gt; environment setting,
       +e.g.: &lt;code&gt;GOTOOLCHAIN=1.23&lt;/code&gt;&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;The default value for &lt;code&gt;GOTOOLCHAIN&lt;/code&gt; is &lt;code&gt;auto&lt;/code&gt;, that basically has the
       +behavior described in this post&lt;/li&gt;
       +&lt;li&gt;You can also set to &lt;code&gt;local&lt;/code&gt; to always use the current &lt;code&gt;go&lt;/code&gt; binary, or the
       +previous behaviour pre-1.21 Go&lt;/li&gt;
       +&lt;li&gt;There is also &lt;code&gt;&amp;lt;name&amp;gt;+auto&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt; options, that can be seen in the
       +docs&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;
       +&lt;p&gt;The downloaded toolchains go to whatever your &lt;code&gt;GOPATH&lt;/code&gt; is, inside
       +&lt;code&gt;golang.org/toolchain&lt;/code&gt; module, and version &lt;code&gt;v0.0.1-goVERSION.GOOS-GOARCH&lt;/code&gt;, for
       +example:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah &lt;span class="nv"&gt;$GOPATH&lt;/span&gt;/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.darwin-arm64
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;total 64
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x  14 user  staff   448B 19 Aug 12:01 .
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;drwxr-xr-x   4 user  staff   128B 19 Aug 12:01 ..
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.3K 19 Aug 12:01 CONTRIBUTING.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.4K 19 Aug 12:01 LICENSE
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.3K 19 Aug 12:01 PATENTS
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   1.4K 19 Aug 12:01 README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   426B 19 Aug 12:01 SECURITY.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff    35B 19 Aug 12:01 VERSION
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x   4 user  staff   128B 19 Aug 12:01 bin
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff    52B 19 Aug 12:01 codereview.cfg
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-r--r--r--   1 user  staff   505B 19 Aug 12:01 go.env
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x   3 user  staff    96B 19 Aug 12:01 lib
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x   4 user  staff   128B 19 Aug 12:01 pkg
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dr-xr-xr-x  77 user  staff   2.4K 19 Aug 12:02 src
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;By the way, this only works well because Go toolchain binaries are static.&lt;/p&gt;
       +&lt;p&gt;While I don't like a program downloading random binaries from the internet, I
       +like what Go is doing here. It makes the whole bootstrapping process for a Go
       +project much easier: as long as you have a reasonable up-to-date &lt;code&gt;go&lt;/code&gt; binary in
       +your &lt;code&gt;PATH&lt;/code&gt;, you should be ready to go (pun intended). And Go modules are
       +already reasonable secure, ensuring that each module have a proper checksum. As
       +long as nobody else can publish modules in &lt;code&gt;golang.org/toolchain&lt;/code&gt; namespace I
       +can't see much of a security issue here, but I am not a security expert.&lt;/p&gt;
       +&lt;p&gt;But if you don't like this behavior, you can always disable it by setting
       +&lt;code&gt;GOTOOLCHAIN=local&lt;/code&gt;. And just do not forget to set this in your
       +&lt;a href="https://brandur.org/fragments/go-version-matrix"&gt;CI&lt;/a&gt;, unless you don't care
       +about Go versions.&lt;/p&gt;</description><pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-go-automatically-downloads-a-newer-toolchain-if-needed/</guid></item><item><title>An unordered list of things I miss in Go</title><link>https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go/</link><description>&lt;p&gt;I like Go. I think it is a &lt;a href="https://kokada.dev/blog/go-a-reasonable-good-language"&gt;reasonable good
       +language&lt;/a&gt;, and has some
       +good qualities that makes up for its flaws. However, this doesn't mean I think
       +the language couldn't be better, far from it.&lt;/p&gt;
       +&lt;p&gt;This blog post is a list of things that I miss from Go from other languages.
       +Some of the things here could probably be implemented soon, some other would
       +probably need a major revision of the language. The list is unordered, because
       +this makes it easier for me to update in the future if I found something else,
       +but also because I don't want to think too hard about giving each point here a
       +rank.&lt;/p&gt;
       +&lt;p&gt;With all above, let's start.&lt;/p&gt;
       +&lt;h2&gt;Ordered maps in standard library&lt;/h2&gt;
       +&lt;p&gt;When I first learned about
       +&lt;a href="https://docs.python.org/3/library/stdtypes.html#typesmapping"&gt;dictionaries&lt;/a&gt; in
       +Python it quickly became one of my favorite data structures ever. They're
       +extremely versatile, and most modern programming languages have something
       +similar in its standard library. Go isn't different, it has
       +&lt;a href="https://go.dev/blog/maps"&gt;&lt;code&gt;map&lt;/code&gt;&lt;/a&gt;, that is Go implementation of a &lt;a href="https://en.wikipedia.org/wiki/Hash_table"&gt;hash
       +table&lt;/a&gt;. However &lt;code&gt;map&lt;/code&gt; in Go are
       +quirky, for example:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;foo&amp;#34;&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="s"&gt;&amp;#34;bar&amp;#34;&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="s"&gt;&amp;#34;baz&amp;#34;&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="s"&gt;&amp;#34;qux&amp;#34;&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="s"&gt;&amp;#34;quux&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go run ./test.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;qux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;quux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go run ./test.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;qux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;quux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; go run ./test.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;qux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;quux
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, I don't expect any hash table implementation to keep the order of the
       +elements, but Go actually &lt;a href="https://victoriametrics.com/blog/go-map/"&gt;randomise each map
       +instance&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;But here’s the deal, while the hash function used for maps in Go is
       +consistent across all maps with &lt;strong&gt;the same key type&lt;/strong&gt;, the &lt;code&gt;seed&lt;/code&gt; used by
       +that hash function is different for each map instance. So, when you create a
       +new map, Go generates a random seed just for that map.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;While I understand the reason for this (i.e.: to avoid developers relying in a
       +specific iteration order), I still find it weird, and I think this is something
       +unique for Go. This decision means that even if you don't care about a specific
       +order, you will still need to sort the map before doing something else if you
       +want reproducibility, something that I care a lot.&lt;/p&gt;
       +&lt;p&gt;The fix for this? Go could offer an ordered map implementation inside the
       +standard library. An ordered map ensure that the iteration order of the map is
       +the same as the insertion order (that is, by the way, a powerful property that
       +allow maps to be used in other contexts, not just my pet peeve above).&lt;/p&gt;
       +&lt;p&gt;Python actually does this for any dictionaries since &lt;a href="https://stackoverflow.com/a/39980744"&gt;Python
       +3.6&lt;/a&gt;, but it offered an
       +&lt;a href="https://docs.python.org/3/library/collections.html#collections.OrderedDict"&gt;OrderedDict&lt;/a&gt;
       +before it (and &lt;code&gt;OrderedDict&lt;/code&gt; still has some methods that normal &lt;code&gt;dict&lt;/code&gt; doesn't,
       +that maybe useful in specific cases).&lt;/p&gt;
       +&lt;p&gt;Before generics it would be impossible to have a type-safe API for such data
       +structure without introducing a new data type in the language (like &lt;code&gt;slices&lt;/code&gt;),
       +but now Go has generics so it is not an issue anymore. The other issue is that
       +you would be forced to iterate manually in this new data structure, but thanks
       +to the new &lt;a href="https://tip.golang.org/doc/go1.23#language"&gt;&lt;code&gt;range-over-func&lt;/code&gt;&lt;/a&gt; in
       +Go 1.23, it means we can iterate in an ordered map as a library almost exactly
       +like we can do as a &lt;code&gt;map&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;orderedmap&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;orderedmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;foo&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bar&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;baz&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Iterator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Order always will be: foo, bar, baz
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, of course the lack of Ordered Map in the standard library can be filled
       +with third party implementations, e.g.: I am using this
       +&lt;a href="https://github.com/elliotchance/orderedmap"&gt;one&lt;/a&gt; in one of my projects. But
       +being in standard library reduces the friction: if there was some
       +implementation in standard library, I would generally prefer it unless I have
       +some specific needs. However when the standard library doesn't offer what I
       +need, I need to find it myself a suitable library, and this ends up taking time
       +since generally there are lots of alternatives.&lt;/p&gt;
       +&lt;h2&gt;Keyword and default arguments for functions&lt;/h2&gt;
       +&lt;p&gt;Something that comes straight from Python that I miss sometimes in Go is that
       +you can do things like this when declaring a function:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;World&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;print&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;#34;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#34;normal&amp;#34; function call&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# calling with keyword arguments&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# calling with default arguments&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python hello.py
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, Foo
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, Bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, World
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The lack of default arguments especially affects even some of the API decisions
       +for Go standard library, for example, &lt;code&gt;string.Replace&lt;/code&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;&lt;code&gt;func Replace(s, old, new string, n int) string&lt;/code&gt;&lt;/p&gt;
       +&lt;p&gt;Replace returns a copy of the string s with the first n non-overlapping
       +instances of old replaced by new. If old is empty, it matches at the
       +beginning of the string and after each UTF-8 sequence, yielding up to k+1
       +replacements for a k-rune string. If n &amp;lt; 0, there is no limit on the number
       +of replacements.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;If Go had default arguments, &lt;code&gt;Replace&lt;/code&gt; could have e.g.: &lt;code&gt;func Replace(s, old, new string, n int = -1)&lt;/code&gt; signature, that would mean we don't need
       +&lt;code&gt;strings.ReplaceAll&lt;/code&gt; (since this is basically what this function does, call
       +&lt;code&gt;strings.Replace(s, old, new, -1)&lt;/code&gt;).&lt;/p&gt;
       +&lt;h2&gt;Nullability (or nillability)&lt;/h2&gt;
       +&lt;p&gt;I talked I little about this in &lt;a href="https://kokada.dev/blog/go-a-reasonable-good-language"&gt;my previous post about
       +Go&lt;/a&gt;, but I want to
       +expand here.&lt;/p&gt;
       +&lt;p&gt;First, I don't think the language needs to support the generic solution for
       +nullability, that would be either having proper Union or Sum types. Kotlin
       +AFAIK doesn't support neither, but my 2 years experience with Kotlin showed
       +that just having nullable types already helped a lot in ensuring type safety.&lt;/p&gt;
       +&lt;p&gt;Second, I do feel that Go has less issues with &lt;code&gt;nil&lt;/code&gt; values, than say, Java,
       +because its decision of using zero values instead of &lt;code&gt;nil&lt;/code&gt; in many cases. So
       +for example, a string can never be &lt;code&gt;nil&lt;/code&gt;, however a string pointer can be. This
       +means that this is fine:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// do something with s
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// s maybe nil here, better check first
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Still, I get more &lt;code&gt;panic&lt;/code&gt; for &lt;code&gt;nil&lt;/code&gt; pointer deference than I get in other
       +languages that offer nullables (heck, even Python with
       +&lt;a href="https://www.mypy-lang.org/"&gt;&lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt; is safer).&lt;/p&gt;
       +&lt;p&gt;Sadly this is the change in this post that is more likely to need a completely
       +new revision of the language.
       +&lt;a href="https://github.com/golang/go/issues/49202"&gt;nillability&lt;/a&gt; was proposed before,
       +but it is really unlikely it can be done without breaking backwards
       +compatibility.&lt;/p&gt;
       +&lt;p&gt;It could be done the Java way by adding a &lt;code&gt;nullable&lt;/code&gt; type to the standard
       +library (&lt;a href="https://jcp.org/en/jsr/detail?id=305"&gt;JSR305&lt;/a&gt;), but the fact that
       +&lt;a href="https://stackoverflow.com/questions/2289694/what-is-the-status-of-jsr-305"&gt;JSR305 is considerd
       +dead&lt;/a&gt;
       +by many shows how difficult it is to do something like this without a major
       +change in the language. Dart is the only language that I know that &lt;a href="https://dart.dev/null-safety/understanding-null-safety"&gt;did this
       +successfully&lt;/a&gt;, but
       +definitely it was not without its pains. And the fact that most people that
       +program in Dart probably does because of Flutter (that eventually required
       +newer versions with null-safety) is not a good sign.&lt;/p&gt;
       +&lt;h2&gt;Lambdas&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-08-18&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;Go is a surprising good language for some functional code, thanks to having
       +first class functions and closures. Sadly the syntax doesn't help, since the
       +only way you can use anonymous functions in Go is using &lt;code&gt;func&lt;/code&gt;. Especially if
       +the types are complex, this can result in some convoluted code. Take the
       +example from the &lt;a href="https://go.dev/wiki/RangefuncExperiment"&gt;&lt;code&gt;range-over-func&lt;/code&gt;
       +experiment&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;slices&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yield&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If Go had a syntax for lambdas, especially if we could elide the types, this
       +could be simplified a lot:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;slices&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or even something like this would already help, no special syntax but allowing
       +the types to be elided in an unnamed function:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;slices&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This feature I am still somewhat hopeful that may become a reality in some
       +future version of the language, since they didn't close the
       +&lt;a href="https://github.com/golang/go/issues/21498"&gt;issue&lt;/a&gt; yet, and the discussion
       +about the possibility of this feature is still ongoing.&lt;/p&gt;
       +&lt;h2&gt;Error on unused return values&lt;/h2&gt;
       +&lt;p&gt;&lt;em&gt;Added in 2024-09-12&lt;/em&gt;&lt;/p&gt;
       +&lt;p&gt;In a &lt;a href="https://kokada.dev/blog/my-coding-experience-with-llm"&gt;recent post about
       +LLMs&lt;/a&gt;, I talked about
       +this function that I asked ChatGPT to generate to create a socket that accepted
       +a &lt;a href="https://pkg.go.dev/context"&gt;context&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// Start a goroutine to perform the read
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;// Context was canceled or timed out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="c1"&gt;// Wait for the read to finish
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read finished successfully
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However while reading &lt;a href="https://joeduffyblog.com/2016/02/07/the-error-model/"&gt;this blog
       +post&lt;/a&gt; about &amp;quot;The Error
       +Model&amp;quot;, I realised that this function is lacking error check during
       +&lt;code&gt;conn.SetReadDeadline()&lt;/code&gt; calls:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;// Start a goroutine to perform the read
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Set a short deadline to unblock the Read()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Reset read deadline
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                                &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Make sure that the goroutine is done to avoid leaks
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I could blame the LLM on this, but this is the type of mistake that I could see
       +happening even in a Pull Request done by an actual human.&lt;/p&gt;
       +&lt;p&gt;Ignoring errors is bad, especially because once the error is ignored it is
       +gone, forever. You may have strange issues in the code that are impossible to
       +debug because you can't know about something that doesn't exist. This is one of
       +the reasons I think exceptions are better, since this is the kind of error that
       +would be impossible to ignore if the language had exceptions, and the exception
       +would eventually propagate in your stack until it is captured or you get a
       +crash (with a stack trace to debug the issue).&lt;/p&gt;
       +&lt;p&gt;Now, I don't think this is a fault of using error as values instead of
       +exceptions. Go has the tools to encapsulate the errors and propagate them
       +properly, like it is shown in the fixed code. But the fact that someone can do
       +this mistake without any warning or error from the compiler is bad.&lt;/p&gt;
       +&lt;p&gt;If Go had a warning or error for unused return values, this would be different:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;  &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now in this case it is clear: I am ignoring the error explicitly, probably for
       +a good reason. I can ask during a Pull Request why the committer is ignoring if
       +it is lacking appropriate context, and maybe even ask for some comments to be
       +added why this would be safe. What can't happen is this being ignored by
       +mistake.&lt;/p&gt;
       +&lt;p&gt;I am not completely sure if I want this only for errors or for any unused value
       +though. There are lots of cases where ignoring non-error values is ok, but I
       +also don't see the issue of having something like:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;// ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;  &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;FuncThatReturnIntAndError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If anything, it is clear for whoever is reading this code later that you're
       +only calling this function only for its side-effects.&lt;/p&gt;
       +&lt;p&gt;By the way, yes, &lt;a href="https://github.com/kisielk/errcheck"&gt;errcheck&lt;/a&gt; linter exists,
       +but if the language itself doesn't enforce this it means that there are lots of
       +libraries that are probably mistakenly ignoring errors. And if the library is
       +ignoring errors by mistake, you can't do much when you actually have them.&lt;/p&gt;</description><pubDate>Sat, 17 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/an-unordered-list-of-things-i-miss-in-go/</guid></item><item><title>Quick bits: NixOS's boot.kernelPackages is a attrset</title><link>https://kokada.dev/blog/quick-bits-nixoss-boot-kernelpackages-is-a-attrset/</link><description>&lt;p&gt;I don't know if this is just something that got me by surprise or not, but
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/bb16119a4a7639ebbc91ad0f516b324b0f7c9b68/nixos/modules/system/boot/kernel.nix#L40-L71"&gt;&lt;code&gt;boot.kernelPackages&lt;/code&gt;&lt;/a&gt;
       +option does not receive a derivation like most other packages, but instead
       +receives a function and returns an attribute set with all packages. Here is the
       +documentation:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;This option allows you to override the Linux kernel used by NixOS. Since
       +things like external kernel module packages are tied to the kernel you’re
       +using, it also overrides those. This option is a function that takes Nixpkgs
       +as an argument (as a convenience), and returns an attribute set containing at
       +the very least an attribute kernel. Additional attributes may be needed
       +depending on your configuration. For instance, if you use the NVIDIA X
       +driver, then it also needs to contain an attribute &lt;code&gt;nvidia_x11&lt;/code&gt;.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;The kernel package itself is referenced by the
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/bb16119a4a7639ebbc91ad0f516b324b0f7c9b68/nixos/modules/system/boot/kernel.nix#L331-L332"&gt;&lt;code&gt;kernel&lt;/code&gt;&lt;/a&gt;
       +derivation inside this attribute set:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;nix-repl&amp;gt; nixosConfigurations.sankyuu-nixos.config.boot.kernelPackages.kernel
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;«derivation /nix/store/5zyjvf3qgfk52qmgxh36l4dkr9lf100x-linux-6.10.3.drv»
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The reason for this is because it ensure that things like modules are built
       +with the same kernel version you are booting.&lt;/p&gt;
       +&lt;p&gt;However one less obvious consequence about this is that if you want packages
       +that come from &lt;code&gt;linuxPackages&lt;/code&gt;, say for example
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/os-specific/linux/cpupower/default.nix"&gt;&lt;code&gt;cpupower&lt;/code&gt;&lt;/a&gt;
       +or
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/os-specific/linux/cpupower/default.nix"&gt;&lt;code&gt;turbostat&lt;/code&gt;&lt;/a&gt;,
       +it is better to do:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernelPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpupower&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernelPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turbostat&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;linuxPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpupower&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;linuxPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turbostat&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, I used the later for a long time and never had issues. But technically
       +those packages depends in a specific kernel version for a reason, so getting
       +them from &lt;code&gt;config.boot.kernelPackages&lt;/code&gt; reduces the chance of you having some
       +compatibility issue in the future.&lt;/p&gt;</description><pubDate>Thu, 15 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-nixoss-boot-kernelpackages-is-a-attrset/</guid></item><item><title>Abusing Makefiles for fun and profit</title><link>https://kokada.dev/blog/abusing-makefiles-for-fun-and-profit/</link><description>&lt;p&gt;If you are following this blog for a while, it should be no surprise that most
       +of the workflow in this blog is &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;automated using
       +Go&lt;/a&gt;. I
       +basically write Markdown files with some special rules inside the
       +&lt;a href="https://github.com/thiagokokada/blog"&gt;repository&lt;/a&gt;, commit and push it. In
       +seconds, the CI (currently &lt;a href="https://github.com/thiagokokada/blog/blob/4e3f25485c6682f3e066b219df2290934bc0d256/.github/workflows/go.yml"&gt;GitHub
       +Actions&lt;/a&gt;)
       +will take the latest commit, generate some files (since I use the &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;repository
       +itself&lt;/a&gt; as a
       +backup blog) and publish to the &lt;a href="https://kokada.capivaras.dev/"&gt;capivaras.dev
       +website&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;Now, considering how much about &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt; I talk in this blog,
       +it should be a surprise that the workflow above has &lt;strong&gt;zero&lt;/strong&gt; Nix code inside
       +it. I am not saying this blog will never have it, but I am only going to add if
       +this is necessary, for example if I start using a tool to build this blog that
       +I generally don't expect it to be installed by the machine I am currently
       +using. Go is an exception of this rule since it is relatively straightfoward to
       +install (just download the &lt;a href="https://go.dev/doc/install"&gt;binary&lt;/a&gt;) and because
       +its &lt;a href="https://go.dev/doc/go1compat"&gt;stability guarantee&lt;/a&gt; means (hopefully) no
       +breakage. But most other things I consider moving targets, and I wouldn't be
       +comfortable to use unless I have Nix to ensure reproducibility.&lt;/p&gt;
       +&lt;p&gt;This is why the other tool that this blog (ab)uses during its workflow is
       +&lt;a href="https://en.wikipedia.org/wiki/Make_(software)"&gt;&lt;code&gt;Make&lt;/code&gt;&lt;/a&gt;, one of the oldest
       +build automation tool that exist. It is basically available in any *nix (do not
       +confuse with &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt;) system, from most Linux distros to
       +macOS, by default. So it is the tool I choose to automatise some tasks in this
       +blog, even if I consider writing a &lt;code&gt;Makefile&lt;/code&gt; (the domain-specific language
       +that &lt;code&gt;Make&lt;/code&gt; uses) kind of a lost, dark art.&lt;/p&gt;
       +&lt;p&gt;To be clear, the idea of this post is not to be a &lt;code&gt;Makefile&lt;/code&gt; tutorial. I will
       +explain some basic concepts, but if you want an actual tutorial a good one can
       +be found &lt;a href="https://makefiletutorial.com/"&gt;here&lt;/a&gt;. Also, while I am using &lt;code&gt;Make&lt;/code&gt;
       +thanks to the reasons above, you can use many other tools for a similar
       +objective, like &lt;a href="https://github.com/casey/just"&gt;Justfiles&lt;/a&gt;,
       +&lt;a href="https://taskfile.dev/"&gt;Taskfiles&lt;/a&gt; (sadly it uses
       +&lt;a href="https://kokada.dev/blog/generating-yaml-files-with-nix"&gt;YAML&lt;/a&gt;), or even a
       +small script written in any language you want. The reason that I am writing
       +this post is why you should do it, not how.&lt;/p&gt;
       +&lt;p&gt;A quick recap on how this blog works: inside the
       +&lt;a href="https://github.com/thiagokokada/blog"&gt;repository&lt;/a&gt;, a post is basically a
       +Markdown post following the directory structure below
       +(&lt;a href="https://github.com/thiagokokada/blog/tree/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7"&gt;permalink&lt;/a&gt;
       +for the current version of this blog):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;.
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;...&amp;gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-07
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   ├── 01-quick-bits-is-crostini-a-microvm.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── 02-meta-are-quick-bits-really-quick.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-11
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── 01-building-static-binaries-in-nix.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-12
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   ├── 01-things-i-dont-like-in-my-chromebook-duet-3.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── Screenshot_2024-08-12_20.50.42.png
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── 2024-08-13
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   ├── 01-abusing-makefiles-for-fun-and-profit.md &amp;lt;-- this file
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── .github
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│   └── workflows
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│       └── go.yml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── .gitignore
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── go.mod
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── go.sum
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── LICENSE
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── link_rewriter.go
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── Makefile
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── mataroa.go
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;└── .scripts
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;    └── gen-post.sh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So I just create a new Markdown file following the
       +&lt;code&gt;YYYY-MM-DD/XX-title-slug.md&lt;/code&gt; format. It &lt;strong&gt;must&lt;/strong&gt; start with a &lt;code&gt;h1&lt;/code&gt; header,
       +that will be automatically extract to be used as the post title, but otherwise
       +there is no other formatting rules. It is a highly optionated structure, but
       +the nice thing about being optionated is that we can extract lots of
       +information just from how the files are organised in the filesystem.&lt;/p&gt;
       +&lt;p&gt;Most of the magic that converts those Markdown files to actual blog posts are
       +in the Go files that you can see above: &lt;code&gt;blog.go&lt;/code&gt; is the main logic that walks
       +in the repository and extracts the necessary information, &lt;code&gt;mataroa.go&lt;/code&gt; is
       +responsible for the &lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt; integration (that
       +uses &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt; platform), while &lt;code&gt;link_rewriter.go&lt;/code&gt; is
       +responsible to do some transformations in the Markdown files before posting.&lt;/p&gt;
       +&lt;p&gt;While I could manage everything by just using &lt;code&gt;go&lt;/code&gt; CLI and a few other *nix
       +commands, to make it easier to manager everything I have the following
       +&lt;a href="https://github.com/thiagokokada/blog/blob/527466a2a7c8baae532281bff5db3f0695f018cb/Makefile"&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;MARKDOWN&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;wildcard ./**/*.md&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;README&lt;/span&gt;.&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="n"&gt;rss&lt;/span&gt;.&lt;span class="n"&gt;xml&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;blog&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; *.&lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt;.*
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        go build
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;README.md&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;MARKDOWN&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog &amp;gt; README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;rss.xml&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;MARKDOWN&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog -rss &amp;gt; rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;publish&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog -publish
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DAY&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;shell date&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;_PARSED_DAY&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;shell date &lt;span class="s1"&gt;&amp;#39;+%Y-%m-%d&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;$(DAY)&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        mkdir -p &lt;span class="s1"&gt;&amp;#39;$(_PARSED_DAY)&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TITLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;error TITLE is not defined&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./.scripts/gen-post.sh &lt;span class="s1"&gt;&amp;#39;$(_PARSED_DAY)&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(TITLE)&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;error FILE is not defined&lt;span class="k"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;draft&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(dir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mv &lt;span class="s1"&gt;&amp;#39;$(notdir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.$(notdir $(FILE))&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;undraft&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;undraft&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(dir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mv &lt;span class="s1"&gt;&amp;#39;$(notdir $(FILE))&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$(patsubst .%,%,$(notdir $(FILE)))&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        rm -rf blog
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For those unfamiliar with &lt;code&gt;Makefile&lt;/code&gt;, a quick explanation on how it works from
       +&lt;a href="https://en.wikipedia.org/wiki/Make_(software)#Makefile"&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Each rule begins with a &lt;em&gt;dependency line&lt;/em&gt; which consists of the rule's target
       +name followed by a colon (:) and optionally a list of targets on which the
       +rule's target depends, its prerequisites.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;So if we look for example at the &lt;code&gt;blog&lt;/code&gt; binary, the dependencies are all the
       +&lt;code&gt;.go&lt;/code&gt; files and Go module files like &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt;. We can make the
       +&lt;code&gt;blog&lt;/code&gt; binary by running:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make blog
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One nice thing about &lt;code&gt;Makefile&lt;/code&gt; is that they track if any of the source files
       +has a newer timestamp than the target file, and only trigger the build again if
       +there are changes, for example:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make blog
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;make: &amp;#39;blog&amp;#39; is up to date.
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; touch blog.go
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make blog
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But sometimes this property is undesirable. In those cases we can declare a
       +target as &lt;code&gt;.PHONY&lt;/code&gt;, that basically instructs &lt;code&gt;Makefile&lt;/code&gt; to always make the
       +target. One classic example is &lt;code&gt;clean&lt;/code&gt; target, that removes build artifacts:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make clean
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;rm -rf blog
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make clean
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;rm -rf blog
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By the way, it is better to declare a target as &lt;code&gt;.PHONY&lt;/code&gt; than declaring
       +dependencies incorrectly, especially in languages that has fast build times
       +like e.g.: Go. The worst thing that can happen is something not being rebuild
       +when it needs to. So my recomendation if you are writing your first &lt;code&gt;Makefile&lt;/code&gt;
       +is to just declare everything as &lt;code&gt;.PHONY&lt;/code&gt;. You can always improve it later.&lt;/p&gt;
       +&lt;p&gt;One last basic concept that I want to explain about &lt;code&gt;Makefile&lt;/code&gt; is the default
       +target: it is the target that is run if you just run &lt;code&gt;make&lt;/code&gt; without arguments
       +inside the directory that contains a &lt;code&gt;Makefile&lt;/code&gt;. The default target is
       +generally the first target in the &lt;code&gt;Makefile&lt;/code&gt;. It is common to have an &lt;code&gt;all&lt;/code&gt;
       +target (that is also marked as &lt;code&gt;.PHONY&lt;/code&gt;) that has as dependencies all the
       +targets that you want to build by default. In this particular case I declare
       +the &lt;code&gt;README.md&lt;/code&gt; and &lt;code&gt;rss.xml&lt;/code&gt; files to be build by default, and they themselves
       +depends in &lt;code&gt;blog&lt;/code&gt; binary being build. So once I run &lt;code&gt;make&lt;/code&gt; you get as result:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog &amp;gt; README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./blog -rss &amp;gt; rss.xml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And this result above highlights the first reason I think you should have a
       +&lt;code&gt;Makefile&lt;/code&gt; or something similar in your projects: you don't need to remember
       +the exactly steps that you need to get things working. If I see one project of
       +mine having a &lt;code&gt;Makefile&lt;/code&gt;, I can be reasonably confident that I can get it
       +working by just running &lt;code&gt;make&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;But now let's focus in the other targets that I have in the &lt;code&gt;Makefile&lt;/code&gt; that are
       +not related to the build process but are there to help me manage my blog posts.
       +Remember the rules I explained above? Maybe not, but it should be no problem,
       +because:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ make post TITLE=&amp;quot;My new blog post&amp;quot;
       +mkdir -p &amp;quot;2024-08-13&amp;quot;
       +./.scripts/gen-post.sh &amp;quot;2024-08-13&amp;quot; &amp;quot;My new blog post&amp;quot;
       +Creating file: 2024-08-13/02-my-new-blog-post.md
       +&lt;p&gt;$ cat 2024-08-13/02-my-new-blog-post.md&lt;/p&gt;
       +&lt;h1&gt;My new blog post&lt;/h1&gt;
       +&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
       +&lt;p&gt;This command, &lt;code&gt;make post&lt;/code&gt;, is responsible for:&lt;/p&gt;
       +&lt;ol&gt;
       +&lt;li&gt;Create a new directory for today, if it doesn't exist&lt;/li&gt;
       +&lt;li&gt;Run the
       +&lt;a href="https://github.com/thiagokokada/blog/blob/6a3b06970729f7650e5bee5fb0e1f9f2541ffea8/.scripts/gen-post.sh"&gt;&lt;code&gt;gen-post.sh&lt;/code&gt;&lt;/a&gt;
       +script, that:
       +&lt;ol&gt;
       +&lt;li&gt;Enumerates all posts from the day, so we can number the new post correctly
       +&lt;ul&gt;
       +&lt;li&gt;We already had this post planned for 2024-08-13, so the new post is 02&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;/li&gt;
       +&lt;li&gt;Slugify the title, so we can create each Markdown file with the correct
       +filename&lt;/li&gt;
       +&lt;li&gt;Creates a new Markdown file with the title as a &lt;code&gt;h1&lt;/code&gt; header&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;/li&gt;
       +&lt;/ol&gt;
       +&lt;p&gt;The steps above may or may not seen trivial, and for a while I was doing them
       +manually. But not having to think what is the current date or if I already
       +posted that day or what is the slug is for the title make (pun intended) my
       +like much easier.&lt;/p&gt;
       +&lt;p&gt;Yes, the code is ugly. The way variables works in &lt;code&gt;Make&lt;/code&gt; is that you can
       +declare then inside the &lt;code&gt;Makefile&lt;/code&gt;, but they can be overwritten in the terminal
       +if you pass them. I used this to allow &lt;code&gt;make post&lt;/code&gt; to also work for future
       +posts:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make post &lt;span class="nv"&gt;TITLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Another new blog post&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;DAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2024-12-12
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mkdir -p &amp;#34;2024-12-12&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./.scripts/gen-post.sh &amp;#34;2024-12-12&amp;#34; &amp;#34;Another new blog post&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Creating file: 2024-12-12/01-another-new-blog-post.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So in the above case, &lt;code&gt;DAY&lt;/code&gt; is filled with the value passed in the terminal
       +instead of default (that would be the current day), and &lt;code&gt;_PARSED_DAY&lt;/code&gt; is the
       +day we use to actually create the directory. We can actually pass any date
       +format recognised by
       +&lt;a href="https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html"&gt;&lt;code&gt;date&lt;/code&gt;&lt;/a&gt;,
       +not just &lt;code&gt;YYYY-MM-DD&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;I have 2 other phony targets that I want to talk, &lt;code&gt;draft&lt;/code&gt; and &lt;code&gt;undraft&lt;/code&gt;. They
       +expect a &lt;code&gt;FILE&lt;/code&gt; to be passed, and I use them to either hide or unhide a file:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make draft &lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2024-12-12/01-another-new-blog-post.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mv &amp;#34;2024-12-12/01-another-new-blog-post.md&amp;#34; &amp;#34;2024-12-12/.01-another-new-blog-post.md&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make undraft &lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2024-12-12/.01-another-new-blog-post.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mv &amp;#34;2024-12-12/.01-another-new-blog-post.md&amp;#34; &amp;#34;2024-12-12/01-another-new-blog-post.md&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Why? Because hidden files are &lt;a href="https://github.com/thiagokokada/blog/blob/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7/blog.go#L101-L104"&gt;explicit
       +ignored&lt;/a&gt;
       +during my directory parser to mean they're a draft post and not ready to be
       +published. And the reason I created those targets is because I was tired of
       +trying to hide or unhide a file manually.&lt;/p&gt;
       +&lt;p&gt;So that's it, for the same reason you &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;should probably automate
       +everything&lt;/a&gt;,
       +you also need to have some way to automate your tasks. &lt;code&gt;Makefile&lt;/code&gt; is one way to
       +do it, maybe not the best way to do it, but it works and it is available
       +anywhere.&lt;/p&gt;</description><pubDate>Tue, 13 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/abusing-makefiles-for-fun-and-profit/</guid></item><item><title>Things I don't like in my Chromebook Duet 3</title><link>https://kokada.dev/blog/things-i-dont-like-in-my-chromebook-duet-3/</link><description>&lt;p&gt;So this is kind of a continuation from my &lt;a href="https://kokada.dev/blog/my-favorite-device-is-a-chromebook"&gt;previous
       +post&lt;/a&gt; talking why
       +my favorite device is a Chromebook. In this post I want to talk about what
       +makes me this device unhappy, and comment about things that if changed would
       +make it a much better device.&lt;/p&gt;
       +&lt;p&gt;But before talking about the negative aspects, let me talk about a positive
       +aspect that I just briefly talked in the previous post: the screen. It is a
       +HiDPI screen (2000x1200 resolution in 10.95''), that is unexpected bright (400
       +nits according to the
       +&lt;a href="https://www.lenovo.com/us/en/p/laptops/lenovo/lenovo-edu-chromebooks/ideapad-duet-3-chromebook-11-inch,-qlc/len101i0034"&gt;specs&lt;/a&gt;).
       +It is difficult to find laptops at the same price with a screen that good. At
       +10.95'' in its default resolution I find it too small (like 1250x750), but I
       +find the font size acceptable at 115% scale (1087x652). Yes, it result in a
       +small workspace, but this is not a big issue for what I do in this device. It
       +is also only 60Hz, but I thought I would miss high refresh rate more than I
       +actually miss in this device.&lt;/p&gt;
       +&lt;p&gt;Update: I forgot to say one thing about the screen: it scratches really easy. I
       +got my screen scratched after the first day of usage, and considering the price
       +I don't think the screen has a hardened glass. I bought a cheap glass screen
       +protector and this did the trick though, even hiding the previous scratch, and
       +I have zero issues with the screen now.&lt;/p&gt;
       +&lt;p&gt;Now the first aspect that I don't like: the speakers. They sound tiny and even
       +at maximum volume it is not really loud. The speakers is the only reason why I
       +still keep my &lt;a href="https://www.gsmarena.com/xiaomi_pad_5-11042.php"&gt;Xiaomi Pad 5&lt;/a&gt;,
       +because I like to watch animes/videos before sleep and having good speakers is
       +a must.&lt;/p&gt;
       +&lt;p&gt;The keyboard has that issue that I mentioned in the previous post: sometimes
       +the key get stuck, and I get duplicated characters. But it also has some minor
       +issues that I didn't talked about: the first one is the UK layout that has some
       +extra keys that I have no use for, but this also makes the keys that I use
       +smaller. Very much a &amp;quot;me&amp;quot; problem, since if I had got a US version I wouldn't
       +have those issues, but an issue nonetheless that gets worse considering how
       +small the keyboard is. I am actually suprised how fast I can type considering
       +how many issues this keyboard has, so maybe this is a testament that this
       +keyboard is not actually that bad.&lt;/p&gt;
       +&lt;p&gt;The other keyboard issue is a problem that affects all Chromebooks: its custom
       +layout. Google replaced a few keys like Fn keys with shortcuts and replaced the
       +Caps Lock with a
       +&lt;a href="https://chromeunboxed.com/chromebook-launcher-now-everything-button"&gt;&amp;quot;Everything&amp;quot;&lt;/a&gt;
       +key (that is similar to the Windows Key), while removing Windows Key from its
       +place. I actually have less issue with this than I initially though: I don't
       +care too much about Fn keys (except when using IntelliJ, but that is something
       +that I only use at &lt;code&gt;$CURRENT_JOB&lt;/code&gt;), and ChromeOS is surprisingly powerful in
       +its customisation, allowing you to swap key functionality. I remap Everything
       +key with Esc, and Esc for the Everything key, and I can get productive in my
       +&lt;code&gt;neovim&lt;/code&gt; setup.&lt;/p&gt;
       +&lt;p&gt;And finally, let me talk more about the performance: yes, it is bad, but
       +bearable once you get used to. The issue is both the CPU and IO. While the CPU,
       +a &lt;a href="https://www.qualcomm.com/products/mobile/snapdragon/laptops-and-tablets/snapdragon-mobile-compute-platforms/snapdragon-7c-gen-2-compute-platform"&gt;Snapdragon 7c Gen
       +2&lt;/a&gt;
       +is octa-core, it has only 2 high performance CPU cores vs 6 low performance
       +ones (2xARM Cortex A76 vs 6xARM Cortex A55). If it was something like 4x4, it
       +would be much better. The fact that the cores are old doesn't help either.&lt;/p&gt;
       +&lt;p&gt;But the worst part is the IO. Not only it uses a eMMC module, it is slow:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-12/Screenshot_2024-08-12_20.50.42.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-12/Screenshot_2024-08-12_20.50.42.png" alt="CPDT Benchmark results from Chromebook Duet 3."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;I don't know how much more expensive it would be to put a
       +&lt;a href="https://en.wikipedia.org/wiki/Universal_Flash_Storage"&gt;UFS&lt;/a&gt; instead of eMMC in
       +this device, but this is probably the choice that would most increase
       +performance in this device, especially considering how aggressive Chromebooks
       +use (z)swap.&lt;/p&gt;
       +&lt;p&gt;Update 2: I forgot to talk about the fact that the exterior of the device is
       +covered in cloth. I thought I would hate this at first, but nowadays I kind
       +like it. And it is also nice that it will never get scratched, I don't care too
       +much about the exterior of this device and it is the only device that I have at
       +home that doesn't have additional protection (except the screen protector
       +mentioned above).&lt;/p&gt;</description><pubDate>Mon, 12 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/things-i-dont-like-in-my-chromebook-duet-3/</guid></item><item><title>Building static binaries in Nix</title><link>https://kokada.dev/blog/building-static-binaries-in-nix/</link><description>&lt;p&gt;I recently had laser eye surgery for my near-sightedness, and while evaluating
       +if I could have the surgery I discovered that I was suffering from dry eyes.
       +Thanks to this, my ophthalmologist recommended that every 20 minutes while
       +using screens, I look somewhere 20 feet away for 20 seconds, a technique known
       +as &lt;a href="https://www.healthline.com/health/eye-health/20-20-20-rule"&gt;20-20-20 rule&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;I had issues following this rule because I never remembered to do the pauses. I
       +initially tried to setup an alarm, but this became annoying, so I decided to
       +try to find a program. I found
       +&lt;a href="https://tonyh4156.github.io/20-20-20.github.io/"&gt;this&lt;/a&gt; one for macOS that
       +worked fine, but it bothered me that it was compiled for x86_64 while I was
       +using a M1 MacBook Pro at the time, and also I needed something that worked in
       +Linux.&lt;/p&gt;
       +&lt;p&gt;Since I never found a good multi-platform alternative, I decided to write my
       +own. This became
       +&lt;a href="https://github.com/thiagokokada/twenty-twenty-twenty/"&gt;twenty-twenty-twenty&lt;/a&gt;,
       +the first &lt;a href="https://kokada.dev/blog/go-a-reasonable-good-language"&gt;Go&lt;/a&gt; program
       +that I ever wrote. I wrote it in Go because I wanted to learn the language, but
       +also because Go made it easy to build static binaries. And the first version I
       +could build static binaries without issues because I was using
       +&lt;a href="https://github.com/gen2brain/beeep"&gt;beeep&lt;/a&gt;, that uses pure Go code in all
       +supported platforms. However, it also meant that the notifications in macOS
       +looked ugly, since it used
       +&lt;a href="https://github.com/gen2brain/beeep/blob/master/beep_darwin.go#L20"&gt;osascript&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;I wanted better integration with macOS, so this meant switching libraries.
       +After searching for a while, the
       +&lt;a href="https://pkg.go.dev/gioui.org/x/notify"&gt;notify&lt;/a&gt; library from
       +&lt;a href="https://gioui.org/"&gt;GioUI&lt;/a&gt; is the one that seemed to work better. It
       +implements notification in macOS using its native framework, so it works much
       +better, but sadly it meant losing static binaries because it depends in CGO.&lt;/p&gt;
       +&lt;p&gt;Not a big loss initially, because I am only depending in Foundation inside
       +macOS (that should always be available), and in Linux I could still statically
       +compile. However I eventually added more features like sound
       +(via &lt;a href="https://github.com/gopxl/beep"&gt;beep&lt;/a&gt;) and tray icon (via
       +&lt;a href="https://github.com/fyne-io/systray"&gt;systray&lt;/a&gt;), that meant I needed CGO in both
       +macOS and Linux.&lt;/p&gt;
       +&lt;p&gt;Losing static binaries in Linux is a much bigger deal, since Linux is a moving
       +target. The general recommendation for building CGO binaries statically is
       +using
       +&lt;a href="https://eli.thegreenplace.net/2024/building-static-binaries-with-go-on-linux/"&gt;musl&lt;/a&gt;,
       +but this also means building all dependencies that we need using musl (e.g.:
       +&lt;a href="https://github.com/ebitengine/oto?tab=readme-ov-file#linux"&gt;&lt;code&gt;ALSA&lt;/code&gt;&lt;/a&gt; for
       +[beep/oto]). This generally means pain, but Nix makes it easy.&lt;/p&gt;
       +&lt;p&gt;Let's start by creating a &lt;a href="https://github.com/thiagokokada/twenty-twenty-twenty/blob/main/twenty-twenty-twenty.nix"&gt;Nix
       +file&lt;/a&gt;
       +that builds our Go module (simplified below for brevity):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdenv&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alsa-lib&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buildGoModule&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pkg-config&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;buildGoModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanSource&lt;/span&gt; &lt;span class="sr"&gt;./.&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;vendorHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sha256-NzDhpJRogIfL2IYoqAUHoPh/ZdNnvnhEQ+kn8A+ZyBw=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;CGO_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optionals&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLinux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkg-config&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optionals&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLinux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;alsa-lib&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;ldflags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-X=main.version=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-s&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-w&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optionals&lt;/span&gt; &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-linkmode external&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;-extldflags &amp;#34;-static&amp;#34;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alerts every 20 minutes to look something at 20 feet away for 20 seconds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/thiagokokada/twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;licenses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;mainProgram&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we can build it with the following &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;twenty-twenty-twenty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nixpkgs/nixpkgs-unstable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;flake-compat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:edolstra/flake-compat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;supportedSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aarch64-linux&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# Helper function to generate an attrset &amp;#39;{ x86_64-linux = f &amp;#34;x86_64-linux&amp;#34;; ... }&amp;#39;.&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;genAttrs&lt;/span&gt; &lt;span class="n"&gt;supportedSystems&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# Nixpkgs instantiated for supported system types.&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;nixpkgsFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nixpkgsFor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;twenty-twenty-twenty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;twenty-twenty-twenty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callPackage&lt;/span&gt; &lt;span class="sr"&gt;./twenty-twenty-twenty.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;twenty-twenty-twenty-static&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgsStatic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callPackage&lt;/span&gt; &lt;span class="sr"&gt;./twenty-twenty-twenty.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;});&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I think this shows how powerful Nix is: the only difference between the normal
       +build and a static build the usage of &lt;code&gt;pkgs.pkgsStatic&lt;/code&gt; instead of &lt;code&gt;pkgs&lt;/code&gt;. This
       +automatically builds all packages statically with &lt;code&gt;musl&lt;/code&gt;. Also we need pass
       +some &lt;a href="https://honnef.co/articles/statically-compiled-go-programs-always-even-with-cgo-using-musl/"&gt;extra
       +flags&lt;/a&gt;
       +to the Go compiler (i.e.: &lt;code&gt;-linkmode external -extldflags &amp;quot;-static&amp;quot;&lt;/code&gt;), but this
       +is a requirement from Go.&lt;/p&gt;
       +&lt;p&gt;So, does it work? Let's test:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#twenty-twenty-twenty-static
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; file result/bin/twenty-twenty-twenty
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;result/bin/twenty-twenty-twenty: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./result/bin/twenty-twenty-twenty
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/ly9d7llymzjyf6gi1455qzayqipk2kab-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/ly9d7llymzjyf6gi1455qzayqipk2kab-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ALSA lib pcm.c:2712:(snd_pcm_open_conf) Either /nix/store/ly9d7llymzjyf6gi1455qzayqipk2kab-pipewire-1.2.1/lib/alsa-lib/libasound_module_pcm_pipewire.so cannot be opened or _snd_pcm_pipewire_open was not defined inside
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2024-08-11T19:26:33+01:00 INFO Running twenty-twenty-twenty every 20.0 minute(s), with 20 second(s) duration and sound set to true
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are some warns and sadly the sound doesn't work. I think the issue is
       +related because of my usage of PipeWire and the binary may work in a pure ALSA
       +system, but I don't have access to one. Maybe adding &lt;code&gt;pipewire&lt;/code&gt; to
       +&lt;code&gt;buildInputs&lt;/code&gt; would fix this issue, but I can't get &lt;code&gt;pipewire&lt;/code&gt; to be compiled
       +statically (because of its dependencies). I think this is a good show how easy
       +it is to statically compilation is in Nix, but also how complex static binaries
       +are to get correctly.&lt;/p&gt;
       +&lt;p&gt;Bonus points for
       +&lt;a href="https://nix.dev/tutorials/cross-compilation.html"&gt;cross-compilation&lt;/a&gt;. We can
       +easily cross-compile by using &lt;code&gt;pkgsCross&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forAllSystems&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nixpkgsFor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;twenty-twenty-twenty-static-aarch64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgsCross&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aarch64-multiplatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgsStatic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callPackage&lt;/span&gt; &lt;span class="sr"&gt;./twenty-twenty-twenty.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;withStatic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;});&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea of &lt;code&gt;pkgsCross&lt;/code&gt; is to select a target platform (e.g.:
       +&lt;code&gt;aarch64-multiplatform&lt;/code&gt;) and use it as any other &lt;code&gt;pkgs&lt;/code&gt;. We can even chain
       +&lt;code&gt;pkgsStatic&lt;/code&gt; to statically cross compile binaries:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#twenty-twenty-twenty-static-aarch64
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; file result/bin/twenty-twenty-twenty
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;result/bin/twenty-twenty-twenty: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description><pubDate>Sun, 11 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/building-static-binaries-in-nix/</guid></item><item><title>Meta: are quick bits really quick?</title><link>https://kokada.dev/blog/meta-are-quick-bits-really-quick/</link><description>&lt;p&gt;When I wrote my first &lt;a href="https://kokada.dev/blog/quick-bits-nix-shell-is-cursed"&gt;quick
       +bits&lt;/a&gt; post in this
       +blog, I gave that title prefix without much thought: I knew it was supposed to
       +be a quicker post than my previous one, but I never thought too deeply about
       +it. But after:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah **/*quick-bits*.md &lt;span class="p"&gt;|&lt;/span&gt; wc -l
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;4
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, 4 blog posts starting with the same prefix, I was curious: are those
       +quick bits really quick, or at least quicker? Let's see:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ wc -w **/*.md
       + 1107 2024-07-26/01-writing-nixos-tests-for-fun-and-profit.md
       + 1220 2024-07-26/02-using-github-as-a-bad-blog-platform.md
       +  286 2024-07-27/01-quick-bits-nix-shell-is-cursed.md
       +  387 2024-07-29/01-quick-bits-why-you-should-automate-everything.md
       + 1060 2024-07-29/02-go-a-reasonable-good-language.md
       + 1380 2024-07-30/01-first-impressions-fpgbc.md
       + 1238 2024-07-31/01-generating-yaml-files-with-nix.md
       + 2308 2024-08-01/01-troubleshooting-zsh-lag-and-solutions-with-nix.md
       +  504 2024-08-01/02-quick-bits-realise-nix-symlinks.md
       +  834 2024-08-04/01-make-nixd-modules-completion-work-anywhere-with-flakes.md
       + 1147 2024-08-05/01-my-favorite-device-is-a-chromebook.md
       +  394 2024-08-07/01-quick-bits-is-crostini-a-microvm.md
       +  120 README.md
       +11985 total
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;While using &lt;code&gt;wc&lt;/code&gt; is probably not the best way to measure word count (especially
       +in this blog, since I tend to write lots of code snippets), I think this at
       +least it gives me a good insight: yes, quick bits are quicker, and they're
       +basically posts with a soft limit around 500 words. So expect in future this
       +limit to be used.&lt;/p&gt;
       +&lt;p&gt;By the way, at:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; wc -w 2024-08-07/02-meta-are-quick-bits-really-quick.md
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;220 2024-08-07/02-meta-are-quick-bits-really-quick.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This post is also technically a quick bits post, but &amp;quot;quick bits meta&amp;quot; would be
       +too much. And yes, that last block of code is also meta ;).&lt;/p&gt;</description><pubDate>Wed, 07 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/meta-are-quick-bits-really-quick/</guid></item><item><title>Quick bits: is Crostini a micro VM?</title><link>https://kokada.dev/blog/quick-bits-is-crostini-a-micro-vm/</link><description>&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I am not a Virtual Machine specialist, this post is mainly the
       +conclusion I got after some searching.&lt;/p&gt;
       +&lt;p&gt;Someone asked me in my &lt;a href="https://kokada.dev/blog/my-favorite-device-is-a-chromebook"&gt;previous
       +post&lt;/a&gt; about my
       +Chromebook if Crostini could be considered a micro VM. This was a interesting
       +question, so I decided to do another post.&lt;/p&gt;
       +&lt;p&gt;To start, it is really difficult to get a good definition of what a &amp;quot;micro VM&amp;quot;
       +is. &lt;a href="https://firecracker-microvm.github.io/"&gt;Firecracker&lt;/a&gt; defines itself as a
       +micro VM, and describes itself in its website as:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Firecracker is a virtual machine monitor (VMM) that uses the Linux
       +Kernel-based Virtual Machine (KVM) to create and manage microVMs. Firecracker
       +has a minimalist design. It excludes unnecessary devices and guest
       +functionality to reduce the memory footprint and attack surface area of each
       +microVM. This improves security, decreases the startup time, and increases
       +hardware utilization.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Now looking at Crostini, its heart is a VMM called
       +&lt;a href="https://crosvm.dev/"&gt;crosvm&lt;/a&gt;. It is described in its
       +&lt;a href="https://chromium.googlesource.com/chromiumos/platform/crosvm/+/HEAD/README.md"&gt;README&lt;/a&gt;
       +as:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;crosvm is a virtual machine monitor (VMM) based on Linux’s KVM hypervisor,
       +with a focus on simplicity, security, and speed. crosvm is intended to run
       +Linux guests, originally as a security boundary for running native
       +applications on the ChromeOS platform. Compared to QEMU, crosvm doesn’t
       +emulate architectures or real hardware, instead concentrating on
       +paravirtualized devices, such as the virtio standard.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;Similar descriptions right? Actually Firecracker website says it &amp;quot;started from
       +Chromium OS's Virtual Machine Monitor, crosvm, an open source VMM written in
       +Rust&amp;quot;. So I would say it is safe to say crosvm itself is a micro VM.&lt;/p&gt;
       +&lt;p&gt;But
       +&lt;a href="https://www.chromium.org/chromium-os/developer-library/guides/containers/containers-and-vms/"&gt;Crostini&lt;/a&gt;
       +itself is a combination of virtualization AND containerization. Basically
       +inside the VM it runs a Linux kernel and &lt;a href="https://linuxcontainers.org/"&gt;LXC&lt;/a&gt;,
       +that can start arbitrary containers inside it. From the Crostini documentation
       +this choice seems to be to keep startup times down, and also to increase
       +security (e.g.: in case of a security issue inside the container).&lt;/p&gt;
       +&lt;p&gt;This is definitely an interesting choice, since containers allow the overhead
       +of each distro that you run inside Crostini to be low, and the main VM itself
       +(called
       +&lt;a href="https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/HEAD/project-termina/"&gt;Termina&lt;/a&gt;)
       +should have low overhead too thanks to crosvm.&lt;/p&gt;
       +&lt;p&gt;By the way, if you want to learn more how &amp;quot;devices&amp;quot; works inside a micro VM
       +like crosvm, I recommend &lt;a href="https://prilik.com/blog/post/crosvm-paravirt/"&gt;this blog
       +post&lt;/a&gt; talking about
       +paravirtualized devices in crosvm.&lt;/p&gt;</description><pubDate>Wed, 07 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-is-crostini-a-micro-vm/</guid></item><item><title>My favorite device is a Chromebook</title><link>https://kokada.dev/blog/my-favorite-device-is-a-chromebook/</link><description>&lt;p&gt;Most of the posts in this blog (including this one) and most of I would call
       +&amp;quot;personal computing&amp;quot; that I do nowadays is mostly done in one of the most
       +unremarkable devices that I own: a cheap &lt;a href="https://www.lenovo.com/us/en/p/laptops/lenovo/lenovo-edu-chromebooks/ideapad-duet-3-chromebook-11-inch,-qlc/len101i0034"&gt;Chromebook Duet
       +3&lt;/a&gt;,
       +that I bought for around EUR300. I was thinking why, because it is woefully
       +underpowered: a &lt;a href="https://www.anandtech.com/show/16696/qualcomm-announces-snapdragon-7c-gen-2-entrylevel-pc-and-chromebook-refresh"&gt;Snapdragon 7c Gen
       +2&lt;/a&gt;,
       +a CPU that was already considered slow 3 years ago, coupled with an eMMC for
       +storage, that is not much faster than a HDD. At least I have the 8GB RAM
       +version instead of the 4GB one.&lt;/p&gt;
       +&lt;p&gt;It is a hybrid device, one that can be used as either a tablet or laptop, but
       +it is compromised experience in both cases: as a tablet, it lacks the better
       +touch optimised interface from iOS or Android; as a laptop, you have to depend
       +on the stand to adjust the screen, and the detachable keyboard is worse than
       +any laptop I have ever owned: getting keys stucked and characters being
       +duplicated as a result is a common occurence. It is not so bad that I can't get
       +things done though. About the trackpad: its biggest quality is that I never
       +feel the need to use the touchscreen in laptop mode, that is to say that it is
       +acceptable. Just crank up the pointer speed in ChromeOS settings, otherwise you
       +never get anywhere since the trackpad is so small. There is also an active
       +stylus, that helped me sometimes when I needed to sign something but otherwise
       +I can't comment too much.&lt;/p&gt;
       +&lt;p&gt;But I really love this device. It is generally the only device that I bring in
       +trips nowadays, because while it is compromised it works well enough: I can use
       +to consume media in tablet mode (the fact that ChromeOS supports Android apps
       +is a plus in those cases), browse the web and even do Linux stuff (more about
       +this later). The fact that it is small (the size remembers me of a
       +&lt;a href="https://en.wikipedia.org/wiki/Netbook"&gt;netbook&lt;/a&gt;), lightweight (~1KG, including
       +the keyboard), has a good screen (that is bright and HiDPI) and good battery
       +life (I don't have numbers but I almost never worry about it) is what makes
       +this device the perfect companion to trips.&lt;/p&gt;
       +&lt;p&gt;Also, it has 2 USB-C ports and supports DisplayPort alt-mode, so it means you
       +can charge it, connect to a external display and peripherals, all at the same
       +time. Sadly, the maximum output resolution I got was 1080p (2560x1080),
       +although some people at Reddit &lt;a href="https://www.reddit.com/r/chromeos/comments/zh27tg/comment/izku724/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button"&gt;seems to have
       +success&lt;/a&gt;
       +at 1440p, and the specs suggests it supports 4k. It may be my Dell S3423DWC
       +monitor being wonky, the fact that it is Ultrawide or the cable, who knows? I
       +even tried to change the monitor to &amp;quot;High Resolution&amp;quot; mode in settings, but to
       +no avail.&lt;/p&gt;
       +&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; looking at the &lt;a href="https://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/prod_brief_qcom_sd7c_gen2.pdf"&gt;product
       +brief&lt;/a&gt;
       +for Snapdragon 7c Gen 2, it seems it supports up to QHD@60Hz (1440p) for the
       +external display, and not 4k. This explains why it doesn't work at maximum
       +resolution in my Dell S3423DWC, since while it is 1440p it is Ultrawide, so the
       +resolution is bigger than QHD (3440x1440 vs 2560x1440).&lt;/p&gt;
       +&lt;p&gt;ChromeOS is also really interesting nowadays. To start, it is designed from the
       +ground up to be a &lt;a href="https://support.google.com/chromebook/answer/3438631"&gt;secure computing
       +environment&lt;/a&gt;, probably
       +the most secure OS for consumers right now. Being a Chrome-first OS makes it a
       +compromised experience, for example, it is the only device that I use Chrome as
       +my main browser (since I personally prefer Firefox). But having a OS that boots
       +fast is great: I never worry about OS updates because I know the device will be
       +ready in seconds after a reboot. And the whole desktop experience inside the
       +ChromeOS desktop is good, having shortcuts for many operations so you can get
       +things done fast, and support for virtual desktops (ChromeOS call it &amp;quot;desks&amp;quot;)
       +means you can organise your windows as much as you want.&lt;/p&gt;
       +&lt;p&gt;And what I think makes ChromeOS really powerful is
       +&lt;a href="https://chromeos.dev/en/linux"&gt;Crostini&lt;/a&gt;, a full Linux VM that you can run
       +inside ChromeOS. It runs Debian (it seems you can &lt;a href="https://www.reddit.com/r/Crostini/wiki/howto/run-other-distros/"&gt;run other
       +distros&lt;/a&gt;
       +though) with a deep integration with ChromeOS, so you can run even graphical
       +programs without issues (including OpenGL!):&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.22.29.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.22.29.png" alt="Fastfetch inside Crostini with gitk running side-by-side."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.39.58.png"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-08-05/Screenshot_2024-08-05_21.39.58.png" alt="Running glxgears inside Crostini."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;This is all thanks to
       +&lt;a href="https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/vm_tools/sommelier/README.md"&gt;Sommelier&lt;/a&gt;,
       +a nested Wayland compositor that runs inside Crostini and allow both Wayland
       +and X11 applications to be forwarded to ChromeOS. The integration is so good
       +that I can run Firefox inside Crostini and works well enough, but sadly Firefox
       +is too slow in this device (I am not sure if the issue is ChromeOS or Firefox,
       +but I suspect the later since Google does some optimisation per device).&lt;/p&gt;
       +&lt;p&gt;One interesting tidbit about the OpenGL situation in this device: this seems to
       +be the first Chromebook to ship with open source drivers, thanks to Freedreno.
       +There is &lt;a href="https://www.youtube.com/watch?v=8mnjSmN03VM"&gt;this&lt;/a&gt; very interesting
       +presentation done by Rob Clark in XDC 2021, that I recommended anyone
       +interested in free drivers to watch (the reference design of Duet 3 is
       +&lt;a href="https://chromeunboxed.com/chromebook-tablet-snapdragon-7c-homestar-coachz-strongbad"&gt;Strongbad&lt;/a&gt;).&lt;/p&gt;
       +&lt;p&gt;The Crostini integration is probably the best VM integration with Linux I ever
       +saw in an OS: you can manage files inside the VM, share directories between the
       +OS and VM, copy and paste works between the two, GUI applications installed
       +inside the VM appear in the ChromeOS menu, memory allocation inside the VM is
       +transparent, etc. Even the themes for Linux GUI applications are customised to
       +match ChromeOS. It is unironically one of the best Linux desktop experiences I
       +ever had.&lt;/p&gt;
       +&lt;p&gt;Of course I am using Nix, but since the Crostini integration depends on some
       +services being configured and installed, I decided to run Nix inside Debian
       +instead of NixOS and run &lt;a href="https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone"&gt;Home-Manager
       +standalone&lt;/a&gt;.
       +I recommend checking the official &lt;a href="https://wiki.nixos.org/wiki/Installing_Nix_on_Crostini"&gt;NixOS Wiki article about
       +Crostini&lt;/a&gt;, that details
       +how to register applications in ChromeOS (so desktop applications appear in
       +menu) and use &lt;a href="https://github.com/nix-community/nixGL"&gt;nixGL&lt;/a&gt; to make OpenGL
       +applications work.&lt;/p&gt;
       +&lt;p&gt;Like I said at the start of the article, the device is woefully slow thanks to
       +its CPU and eMMC. It does mean that, for example, activating my Home-Manager
       +configuration takes a while (around 1 minute, vs a few seconds in my laptop).
       +But it is much faster than say,
       +&lt;a href="https://github.com/nix-community/nix-on-droid-app"&gt;nix-on-droid&lt;/a&gt;, that the
       +last time I tried in a much more powerful device (&lt;a href="https://www.gsmarena.com/xiaomi_pad_5-11042.php"&gt;Xiaomi Pad
       +5&lt;/a&gt;), took 30 minutes until I
       +just decided to cancel the operation. Having a proper VM instead of
       +&lt;a href="https://wiki.termux.com/wiki/PRoot"&gt;proot&lt;/a&gt; makes all the difference here.&lt;/p&gt;
       +&lt;p&gt;I can even do some light programming here: using my
       +&lt;a href="https://kokada.dev/blog/troubleshoting-zsh-lag-and-solutions-with-nix"&gt;ZSH&lt;/a&gt;
       +and neovim configuration (including LSP for coding) is reasonable fast. For
       +example, I did most of the code that &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;powers this
       +blog&lt;/a&gt;
       +using this Chromebook. If I need more power, I can use the &lt;a href="https://tailscale.com/kb/1267/install-chromebook"&gt;Tailscale app for
       +Android&lt;/a&gt; to connect to any
       +other of my hosts via SSH. Yes, the Tailscale app works in Crostini, sadly
       +without MagicDNS, so you need to use the internal Tailscale IPs instead.&lt;/p&gt;
       +&lt;p&gt;Until Google decides to give us a proper VM or user namespaces in Android or
       +release a hybrid Chromebook device with better specs, this small Chromebook
       +will probably stay as my travel companion, and is one of my favorite devices.&lt;/p&gt;</description><pubDate>Mon, 05 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/my-favorite-device-is-a-chromebook/</guid></item><item><title>Make nixd module completion to work anywhere (with Flakes)</title><link>https://kokada.dev/blog/make-nixd-module-completion-to-work-anywhere-with-flakes/</link><description>&lt;p&gt;If you want the TL;DR, go to the bottom of the post (search for &amp;quot;final
       +result&amp;quot;).&lt;/p&gt;
       +&lt;p&gt;I recently switched from &lt;a href="https://github.com/oxalica/nil"&gt;nil&lt;/a&gt; to
       +&lt;a href="https://github.com/nix-community/nixd"&gt;nixd&lt;/a&gt; as my LSP of choice for Nix. I
       +was curious in &lt;code&gt;nixd&lt;/code&gt; for a long time since the fact that it can eval Nix code
       +means it can offer much more powerful completion than the &lt;code&gt;nil&lt;/code&gt;'s static
       +analysis, however it used to be difficult to setup. Nowadays it is much easier,
       +basically doing the right thing as long as you have &lt;code&gt;NIX_PATH&lt;/code&gt; setup, and you
       +get both package and NixOS modules completion.&lt;/p&gt;
       +&lt;p&gt;Getting Home-Manager modules though needs some setup. The recommended way to
       +setup accordingly to the &lt;a href="https://github.com/nix-community/nixd/blob/fe202307eaf7e89c4366ed927af761482a6065c8/nixd/docs/configuration.md"&gt;official
       +documentation&lt;/a&gt;
       +is to use the following for Flake based configurations (using neovim
       +configuration here, but it should be easy to adapt to other editors):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;import &amp;lt;nixpkgs&amp;gt; { }&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(builtins.getFlake (&amp;#34;git+file://&amp;#34; + toString ./.)).nixosConfigurations.miku-nixos.options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;home_manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(builtins.getFlake (&amp;#34;git+file://&amp;#34; + toString ./.)).homeConfigurations.home-linux.options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;-- ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works, but it should be pretty clear the limitations of using &lt;code&gt;./.&lt;/code&gt;: this
       +will only work if you open your editor in your &lt;a href="https://github.com/thiagokokada/nix-configs"&gt;Nix configuration
       +repository&lt;/a&gt;. For any other
       +repository, &lt;code&gt;nixosConfigurations.miku-nixos&lt;/code&gt; or &lt;code&gt;homeConfigurations.home-linux&lt;/code&gt;
       +will not exist and the completion will not work.&lt;/p&gt;
       +&lt;p&gt;It may look like this is easy to fix if you have
       +&lt;a href="https://nixos.org/manual/nixos/unstable/options#opt-_module.args"&gt;&lt;code&gt;specialArgs&lt;/code&gt;&lt;/a&gt;
       +(or &lt;code&gt;extraSpecialArgs&lt;/code&gt; in Home-Manager) set to have your
       +Flakes inputs, but:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# By the way, ${self} does not exist in the Flake output by default, you need&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# to explicit add `inherit self` to your outputs:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# https://discourse.nixos.org/t/who-is-self-in-flake-outputs/31859/4&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;nix-repl&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git+file://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;miku-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="n"&gt;while&lt;/span&gt; &lt;span class="n"&gt;calling&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;getFlake&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;builtin&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="err"&gt;«&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="err"&gt;»&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;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git+file://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;             &lt;span class="err"&gt;|&lt;/span&gt;  &lt;span class="err"&gt;^&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="n"&gt;while&lt;/span&gt; &lt;span class="n"&gt;evaluating&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="sd"&gt;git+file:///nix/store/avr1lcmznj8ghynh5vj1kakgfdf0zrxx-source&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;refer&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;store&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;such&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;avr1lcmznj8ghynh5vj1kakgfdf0zrxx-source&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, it was worth a try. Another option would be to:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:thiagokokada/nix-configs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;miku-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Or even something like this&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# However, using ${rev} means this wouldn&amp;#39;t work in dirty Flake repos, since&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ${rev} is not set in those cases&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFlake&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:thiagokokada/nix-configs/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;miku-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But while it works, it is slow, because it needs network to evaluate (and it is
       +impure, since there is no &lt;code&gt;flake.lock&lt;/code&gt;).&lt;/p&gt;
       +&lt;p&gt;The default configuration for &lt;code&gt;nixd&lt;/code&gt; makes NixOS completion work even outside
       +of my configuration repo, and it is fast. How? I decided to take a look at the
       +&lt;code&gt;nixd&lt;/code&gt; source code and found
       +&lt;a href="https://github.com/nix-community/nixd/blob/d938026c55c7c36a6e79afd9627459160b4924ed/nixd/lib/Controller/LifeTime.cpp#L33C11-L35C76"&gt;this&lt;/a&gt;
       +(formatted here for legibility):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;&amp;lt;nixpkgs&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evalModules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;&amp;lt;nixpkgs/nixos/modules/module-list.nix&amp;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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentSystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Interesting, so they're manually loading the modules using &lt;code&gt;evalModules&lt;/code&gt;. As I
       +said above, it depends in &lt;code&gt;NIX_PATH&lt;/code&gt; being correctly set. Can we fix this to
       +use our Flake inputs instead? After some tries in the Nix REPL, I got the
       +following:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evalModules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/nixos/modules/module-list.nix&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentSystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we can adapt this to the neovim configuration:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(let pkgs = import &amp;#34;${inputs.nixpkgs}&amp;#34; { }; in (pkgs.lib.evalModules { modules =  (import &amp;#34;${inputs.nixpkgs}/nixos/modules/module-list.nix&amp;#34;) ++ [ ({...}: { nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This was easy. But the main issue is Home-Manager. How can we fix it? I needed
       +to take a look at the Home-Manager &lt;a href="https://github.com/nix-community/home-manager/blob/master/docs/default.nix#L161-L169"&gt;source
       +code&lt;/a&gt;
       +to find the answer:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;home-manager&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/modules/lib/stdlib-extended.nix&amp;#34;&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evalModules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;home-manager&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/modules/modules.nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The interesting part is: Home-Manager has its own extension of the module
       +system (including &lt;code&gt;evalModules&lt;/code&gt;). This includes e.g.: extra types used in
       +Home-Manager only. Also, we need to disable &lt;code&gt;checks&lt;/code&gt;, otherwise we will hit
       +some validations (e.g.: missing &lt;code&gt;stateVersion&lt;/code&gt;). I am not sure if this causes
       +any issue for module completion yet, I may set it in the future.&lt;/p&gt;
       +&lt;p&gt;And for the final result:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;import &amp;#34;${flake.inputs.nixpkgs}&amp;#34; { }&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(let pkgs = import &amp;#34;${inputs.nixpkgs}&amp;#34; { }; in (pkgs.lib.evalModules { modules =  (import &amp;#34;${inputs.nixpkgs}/nixos/modules/module-list.nix&amp;#34;) ++ [ ({...}: { nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;home_manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(let pkgs = import &amp;#34;${inputs.nixpkgs}&amp;#34; { }; lib = import &amp;#34;${inputs.home-manager}/modules/lib/stdlib-extended.nix&amp;#34; pkgs.lib; in (lib.evalModules { modules =  (import &amp;#34;${inputs.home-manager}/modules/modules.nix&amp;#34;) { inherit lib pkgs; check = false; }; })).options&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yes, it is quite a mouthful, but it makes module completion work in any
       +repository, as long as you're using Flakes. And it is fast, since it doesn't
       +need any network access. Since we are already here, let's define &lt;code&gt;nixpkgs&lt;/code&gt; to
       +not depend in the &lt;code&gt;NIX_PATH&lt;/code&gt; being set too.&lt;/p&gt;</description><pubDate>Sun, 04 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/make-nixd-module-completion-to-work-anywhere-with-flakes/</guid></item><item><title>Troubleshooting: ZSH lag and solutions with Nix</title><link>https://kokada.dev/blog/troubleshoting-zsh-lag-and-solutions-with-nix/</link><description>&lt;p&gt;Inspired by this &lt;a href="https://lock.cmpxchg8b.com/slowterm.html"&gt;blog post from
       +Tavis&lt;/a&gt;, I decided to document my own
       +recent journey of reducing terminal (ZSH) lag startup. This post is way less
       +interesting than the one from Tavis that uses a debugger to patch applications
       +on the fly, but should still be interesting for some. And it also shows how
       +powerful Nix can be for some things.&lt;/p&gt;
       +&lt;p&gt;For context, I have basically 3 systems where I interact with terminal
       +frequently:&lt;/p&gt;
       +&lt;ul&gt;
       +&lt;li&gt;&lt;a href="https://www.notebookcheck.net/Lenovo-ThinkPad-P14s-Gen-1-Laptop-Review-AMD-workstation-sans-dedicated-GPU.528746.0.html"&gt;Thinkpad P14s Gen 1&lt;/a&gt;
       +running NixOS, with a reasonable fast CPU (&lt;a href="https://www.notebookcheck.net/AMD-Ryzen-7-PRO-4750U-Processor-Benchmarks-and-Specs.464396.0.html"&gt;AMD Ryzen 7 PRO
       +4750U&lt;/a&gt;)
       +and disk (NVMe)&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-m1-pro-10-core-cpu-16-core-gpu-16-2021-specs.html"&gt;MacBook
       +Pro&lt;/a&gt;
       +with a really fast CPU (M1 Pro) and disk, but of course running macOS, being
       +phased-out since this is a job owned machine and I am changing jobs right
       +now, but should be replaced with another one soon™&lt;/li&gt;
       +&lt;li&gt;&lt;a href="https://chromeunboxed.com/lenovo-chromebook-duet-3-review-perfect-sequel"&gt;Chromebook Duet
       +3&lt;/a&gt;
       +running ChromeOS, with slow CPU (&lt;a href="https://www.notebookcheck.net/Qualcomm-Snapdragon-7c-Gen-2-Processor-Benchmarks-and-Specs.554426.0.html"&gt;Snapdragon 7c Gen
       +2&lt;/a&gt;)
       +and disk (eMMC, really?)&lt;/li&gt;
       +&lt;/ul&gt;
       +&lt;p&gt;My experience is similar to Tavis, at around 300ms of startup time I don't care
       +too much, but around 500ms+ is where I start to notice. I never had any issues
       +with startup time in NixOS itself (I had issues with macOS before, but it was
       +not actually the fault of macOS), but in the Chromebook it was awful: 600ms+
       +with &lt;a href="https://www.instabug.com/blog/understanding-cold-hot-and-warm-app-launch-time"&gt;hot
       +start&lt;/a&gt;,
       +while cold start it could take multiple seconds.&lt;/p&gt;
       +&lt;p&gt;We can check how long ZSH takes to start by using:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; zsh -ci &lt;span class="nb"&gt;exit&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;zsh -ic exit  0.04s user 0.10s system 100% cpu 0.143 total
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;-i&lt;/code&gt; flag here is important, because we are interested in the interactive
       +use of ZSH. Without this flag ZSH will ignore your &lt;code&gt;~/.zshrc&lt;/code&gt; file, and the
       +results will be meaningless.&lt;/p&gt;
       +&lt;p&gt;To do a more interesting benchmark, we can use
       +&lt;a href="https://github.com/sharkdp/hyperfine"&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;zsh -ic exit&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: zsh -ic exit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):     145.4 ms ±   4.2 ms    [User: 49.8 ms, System: 97.3 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):   138.6 ms … 155.3 ms    19 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hyperfine will run the command multiple times and take care of things like
       +shell startup time. A really great tool to have in your toolbox by the way, but
       +I digress.&lt;/p&gt;
       +&lt;p&gt;So let's do a little time travelling. Going back to commit
       +&lt;a href="https://github.com/thiagokokada/nix-configs/tree/b12757f90889653e359a1ab0a8cfd2f90cfabf31"&gt;&lt;code&gt;b12757f&lt;/code&gt;&lt;/a&gt;
       +from &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;nix-configs&lt;/a&gt;. Running
       +&lt;code&gt;hyperfine&lt;/code&gt; like above from my NixOS laptop, we have:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;zsh -ic exit&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: zsh -ic exit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):     218.6 ms ±   5.1 ms    [User: 70.6 ms, System: 151.5 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):   210.3 ms … 227.0 ms    13 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This doesn't look that bad, but let's see the same commit in my Chromebook:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;zsh -ic exit&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: zsh -ic exit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):     679.7 ms ±  40.2 ms    [User: 230.8 ms, System: 448.5 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):   607.3 ms … 737.0 ms    10 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yikes, this is much worse. And those are the results after I retried the
       +benchmark (so it is a hot start). The cold start times were above 3s. So let's
       +investigate what is happening here. We can profile what is taking time during
       +the startup of ZSH using &lt;a href="https://www.bigbinary.com/blog/zsh-profiling"&gt;zprof&lt;/a&gt;.
       +You can add the following in your &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# At the top of your ~/.zshrc file&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zmodload zsh/zprof
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# At the end of your ~/.zshrc file&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zprof
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or if using Home-Manager, use the
       +&lt;a href="https://nix-community.github.io/home-manager/options.xhtml#opt-programs.zsh.zprof.enable"&gt;&lt;code&gt;programs.zsh.zprof.enable&lt;/code&gt;&lt;/a&gt;
       +option. Once we restart ZSH, we will have something like:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;num  calls                time                       self            name
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-----------------------------------------------------------------------------------
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 1)    1          36.91    36.91   34.29%     30.47    30.47   28.31%  (anon) [/home/thiagoko/.zsh/plugins/zim-completion/init.zsh:13]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 2)    1          25.43    25.43   23.63%     25.43    25.43   23.63%  (anon) [/home/thiagoko/.zsh/plugins/zim-ssh/init.zsh:6]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 3)    1          22.00    22.00   20.45%     21.92    21.92   20.36%  _zsh_highlight_load_highlighters
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 4)    1          12.32    12.32   11.45%     12.32    12.32   11.45%  autopair-init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 5)    1           6.44     6.44    5.98%      6.44     6.44    5.98%  compinit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 6)    1           3.56     3.56    3.31%      3.48     3.48    3.23%  prompt_pure_state_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 7)    2           3.79     1.89    3.52%      2.85     1.43    2.65%  async
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 8)    1           0.93     0.93    0.87%      0.93     0.93    0.87%  async_init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 9)    6           0.93     0.15    0.86%      0.93     0.15    0.86%  is-at-least
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;10)    6           0.67     0.11    0.63%      0.67     0.11    0.63%  add-zle-hook-widget
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;11)    1           8.25     8.25    7.66%      0.61     0.61    0.57%  prompt_pure_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;12)    1           0.40     0.40    0.37%      0.40     0.40    0.37%  (anon) [/nix/store/p1zqypy7600fvfyl1v571bljx2l8zhay-zsh-autosuggestions-0.7.0/share/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;13)    5           0.31     0.06    0.29%      0.31     0.06    0.29%  add-zsh-hook
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;14)    1           0.60     0.60    0.56%      0.29     0.29    0.27%  (anon) [/home/thiagoko/.zsh/plugins/zim-input/init.zsh:5]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;15)    1           0.21     0.21    0.20%      0.21     0.21    0.20%  compdef
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;16)    1           0.10     0.10    0.09%      0.10     0.10    0.09%  _zsh_highlight__function_is_autoload_stub_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;17)    1           0.26     0.26    0.24%      0.08     0.08    0.08%  _zsh_highlight__function_callable_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;18)    1           0.08     0.08    0.08%      0.08     0.08    0.08%  prompt_pure_is_inside_container
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;19)    1           0.07     0.07    0.07%      0.07     0.07    0.07%  _zsh_highlight__is_function_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;20)    1           0.01     0.01    0.01%      0.01     0.01    0.01%  __wezterm_install_bash_prexec
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;21)    1           0.00     0.00    0.00%      0.00     0.00    0.00%  _zsh_highlight_bind_widgets
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt; ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I ommited some output for brevit. The first 2 things that shows are from the
       +&lt;a href="https://github.com/zimfw/zimfw"&gt;zimfw&lt;/a&gt;, the framework that I use to configure
       +my ZSH (similar to Oh-My-Zsh). I actually don't use &lt;code&gt;zimfw&lt;/code&gt; directly, instead I
       +just load some modules that I find useful, like the &lt;code&gt;zim-completion&lt;/code&gt; and
       +&lt;code&gt;zim-ssh&lt;/code&gt; that we can see above. By the way, Zim is generally really well
       +optimised for startup time, but those 2 modules are kind slow.&lt;/p&gt;
       +&lt;p&gt;For &lt;a href="https://github.com/zimfw/completion"&gt;&lt;code&gt;zim-completion&lt;/code&gt;&lt;/a&gt;, after taking a
       +look at it, there isn't much I could do. It seems that the reason
       +&lt;code&gt;zim-completion&lt;/code&gt; takes so long during startup is because it is trying to decide
       +if it needs to recompile the completions (and replacing it with just a naive
       +&lt;code&gt;autoload -U compinit &amp;amp;&amp;amp; compinit&lt;/code&gt; is even worse for startup performance). I
       +may eventually replace it for something else, but I really like what Zim brings
       +here, so I decided to not touch it for now.&lt;/p&gt;
       +&lt;p&gt;However &lt;a href="https://github.com/zimfw/ssh"&gt;&lt;code&gt;zim-ssh&lt;/code&gt;&lt;/a&gt; is another history. The only
       +reason I used it is to start a &lt;code&gt;ssh-agent&lt;/code&gt; and keep it between multiple ZSH
       +sessions. It shouldn't have that much influence in startup time. So I took a
       +look the code (since it is small, I am reproducing it here):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set up ssh-agent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Don&amp;#39;t do anything unless we can actually use ssh-agent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;+commands[ssh-agent]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ssh-add -l &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; ? &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Unable to contact the authentication agent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Load stored agent connection info&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; -r &lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/.ssh-agent
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -r &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    ssh-add -l &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; ? &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Start agent and store agent connection info&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;umask&lt;/span&gt; 066&lt;span class="p"&gt;;&lt;/span&gt; ssh-agent &amp;gt;! &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ssh_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Load identities&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ssh-add -l &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; ? &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; -a zssh_ids
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    zstyle -a &lt;span class="s1"&gt;&amp;#39;:zim:ssh&amp;#39;&lt;/span&gt; ids &lt;span class="s1"&gt;&amp;#39;zssh_ids&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;zssh_ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      ssh-add &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/.ssh/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;^zssh_ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 2&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;else&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      ssh-add 2&amp;gt;/dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, this is bad. Let's assume the common path, where the &lt;code&gt;ssh-agent&lt;/code&gt; is
       +already running but you open a new shell instance (that doesn't have the
       +connection info yet so it will need to load). This will run &lt;code&gt;ssh-add&lt;/code&gt; at 4
       +times. How long does &lt;code&gt;ssh-add&lt;/code&gt; takes to run?&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine -Ni &lt;span class="s2"&gt;&amp;#34;ssh-add -l&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: ssh-add -l
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):       4.6 ms ±   1.1 ms    [User: 2.0 ms, System: 2.0 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):     3.4 ms …   8.7 ms    619 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="go"&gt;  Warning: Ignoring non-zero exit code.
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For those curious, &lt;code&gt;-N&lt;/code&gt; disables the Shell usage, that works better when the
       +command being tested is too fast.&lt;/p&gt;
       +&lt;p&gt;In average we have 4x4ms=16ms of startup time. But keep in mind the worst case
       +can be much worse. The question is, how can we improve the situation here?&lt;/p&gt;
       +&lt;p&gt;After taking a look, I decided to write my own code, based in some ideas stolen
       +from &lt;a href="https://github.com/ohmyzsh/ohmyzsh/blob/67581c53c6458566e174620361e84b364b9034d2/plugins/ssh-agent/ssh-agent.plugin.zsh"&gt;Oh-My-Zsh ssh-agent
       +plugin&lt;/a&gt;.
       +Here is final version of my
       +&lt;a href="https://github.com/thiagokokada/nix-configs/blob/e45a888f2bf3ce5644c3966f0b6371414d0291e2/home-manager/cli/ssh/ssh-agent.zsh"&gt;code&lt;/a&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zmodload zsh/net/socket
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_check_agent&lt;span class="o"&gt;(){&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -S &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zsocket &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_start_agent&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Test if $SSH_AUTH_SOCK is visible, in case we start e.g.: ssh-agent via&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# systemd service&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; _check_agent&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Get the filename to store/lookup the environment from&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;local&lt;/span&gt; -r &lt;span class="nv"&gt;ssh_env_cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.ssh-agent&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# Check if ssh-agent is already running&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; /dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Test if $SSH_AUTH_SOCK is visible, e.g.: the ssh-agent is still alive&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; _check_agent&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# start ssh-agent and setup environment&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;(&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;umask&lt;/span&gt; &lt;span class="m"&gt;066&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    ssh-agent -s &amp;gt;! &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ssh_env_cache&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; /dev/null
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_start_agent
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unfunction _check_agent _start_agent
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea here is simple: using
       +&lt;a href="https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fnet_002fsocket-Module"&gt;&lt;code&gt;zsocket&lt;/code&gt;&lt;/a&gt;
       +module from ZSH itself to check if the &lt;code&gt;ssh-agent&lt;/code&gt; is working instead of
       +executing &lt;code&gt;ssh-add -l&lt;/code&gt;. The only case we run any program now is to start the
       +agent itself if needed. Let's run &lt;code&gt;hyperfine&lt;/code&gt; again:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     188.3 ms ±   8.2 ms    [User: 61.1 ms, System: 130.0 ms]
       +  Range (min … max):   170.9 ms … 198.4 ms    16 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Got a good improvement here already. Let's see &lt;code&gt;zprof&lt;/code&gt; again:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;num  calls                time                       self            name
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-----------------------------------------------------------------------------------
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 1)    1          41.23    41.23   48.66%     33.52    33.52   39.56%  (anon) [/home/thiagoko/.zsh/plugins/zim-completion/init.zsh:13]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 2)    1          22.23    22.23   26.24%     22.12    22.12   26.10%  _zsh_highlight_load_highlighters
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 3)    1           8.90     8.90   10.51%      8.90     8.90   10.51%  Gautopair-init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 4)    1           7.71     7.71    9.10%      7.71     7.71    9.10%  compinit
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 5)    1           5.74     5.74    6.77%      5.60     5.60    6.60%  prompt_pure_state_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 6)    6           1.19     0.20    1.41%      1.19     0.20    1.41%  add-zle-hook-widget
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 7)    2           1.97     0.99    2.33%      1.14     0.57    1.34%  async
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 8)    6           0.87     0.15    1.03%      0.87     0.15    1.03%  is-at-least
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 9)    1           0.84     0.84    0.99%      0.84     0.84    0.99%  async_init
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;10)    1           9.30     9.30   10.97%      0.72     0.72    0.84%  prompt_pure_setup
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;11)    5           0.63     0.13    0.75%      0.63     0.13    0.75%  add-zsh-hook
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;12)    1           0.41     0.41    0.48%      0.41     0.41    0.48%  _start_agent
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;13)    1           0.31     0.31    0.37%      0.31     0.31    0.37%  (anon) [/nix/store/p1zqypy7600fvfyl1v571bljx2l8zhay-zsh-autosuggestions-0.7.0/share/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;14)    1           0.55     0.55    0.64%      0.24     0.24    0.28%  (anon) [/home/thiagoko/.zsh/plugins/zim-input/init.zsh:5]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;15)    1           0.14     0.14    0.16%      0.14     0.14    0.16%  prompt_pure_is_inside_container
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;16)    1           0.14     0.14    0.16%      0.14     0.14    0.16%  compdef
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;17)    1           0.09     0.09    0.11%      0.09     0.09    0.11%  _zsh_highlight__function_is_autoload_stub_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;18)    1           0.25     0.25    0.29%      0.08     0.08    0.09%  _zsh_highlight__function_callable_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;19)    1           0.07     0.07    0.09%      0.07     0.07    0.09%  _zsh_highlight__is_function_p
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;20)    1           0.01     0.01    0.01%      0.01     0.01    0.01%  __wezterm_install_bash_prexec
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;21)    1           0.01     0.01    0.01%      0.01     0.01    0.01%  _zsh_highlight_bind_widgets
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt; ...
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, there is nothing interesting here anymore. I mean, &lt;code&gt;zim-completion&lt;/code&gt; is
       +still the main culprit, but nothing to do for now. Instead of looking at
       +&lt;code&gt;zproof&lt;/code&gt;, let's take a look at my &lt;code&gt;~/.zshrc&lt;/code&gt; instead:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$options&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;zle&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; on &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/nix/store/sk6wsgp4h477baxypksz9rl8ldwwh9yg-fzf-0.54.0/bin/fzf --zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/nix/store/x3yblr73r5x76dmaanjk3333mvzxc49r-any-nix-shell-1.2.1/bin/any-nix-shell zsh &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; /dev/stdin
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/nix/store/330d6k81flfs6w46b44afmncxk57qggv-zoxide-0.9.4/bin/zoxide init zsh &lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/nix/store/8l9j9kdv9m0z0s30lp4yvrc9s5bcbgmx-direnv-2.34.0/bin/direnv hook zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So you see, starting all those programs during ZSH startup can hurt the shell
       +startup considerable. Not necessary for commands fast like &lt;code&gt;fzf&lt;/code&gt; (that is
       +written in Go), but let's see
       +&lt;a href="https://github.com/haslersn/any-nix-shell"&gt;&lt;code&gt;any-nix-shell&lt;/code&gt;&lt;/a&gt;, that is written
       +in shell script:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine &lt;span class="s2"&gt;&amp;#34;any-nix-shell zsh&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: any-nix-shell zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):      16.0 ms ±   1.8 ms    [User: 5.6 ms, System: 10.5 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):    11.3 ms …  20.3 ms    143 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is bad, consistently bad actually. Even for commands that are fast, keep
       +in mind that there is a difference between the cold and hot start again. For
       +example, &lt;code&gt;fzf&lt;/code&gt;:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine -N &lt;span class="s2"&gt;&amp;#34;fzf --zsh&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: fzf --zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Time (mean ± σ):       2.9 ms ±   0.9 ms    [User: 0.6 ms, System: 2.3 ms]
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;  Range (min … max):     1.7 ms …   6.8 ms    1113 runs
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;See the range? While 1.7ms is something that is probably difficult to notice,
       +6.8ms can be noticiable, especially if this accumulates with other slow
       +starting apps.&lt;/p&gt;
       +&lt;p&gt;And the thing is, all those commands are just generating in the end a fixed
       +output, at least for the current version of the program. Can we pre-generate
       +them instead? If using Nix, of course we can:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# You need to disable the default integration&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enableZshIntegration&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fzf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enableZshIntegration&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zoxide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enableZshIntegration&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zsh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initExtra&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# bash&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # any-nix-shell
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;any-nix-shell-zsh&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getExe&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;any-nix-shell&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; zsh &amp;gt; $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # fzf
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fzf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/share/fzf/completion.zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fzf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/share/fzf/key-bindings.zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # zoxide
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zoxide-init-zsh&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getExe&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zoxide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; init zsh &amp;gt; $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    # direnv
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;direnv-hook-zsh&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getExe&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; hook zsh &amp;gt; $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we can use &lt;code&gt;pkgs.runCommand&lt;/code&gt; to run those commands during build time and
       +&lt;code&gt;source&lt;/code&gt; the result. &lt;code&gt;fzf&lt;/code&gt; actually doesn't need this since we have the files
       +already generated in the package. I think this is one of those things that
       +really shows the power of Nix: I wouldn't do something similar if I didn't use
       +Nix because the risk of breaking something later is big (e.g.: forgetting to
       +update the generated files), but Nix makes those things trivial.&lt;/p&gt;
       +&lt;p&gt;Let's run &lt;code&gt;hyperfine&lt;/code&gt; again:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     162.3 ms ±   4.9 ms    [User: 52.7 ms, System: 111.1 ms]
       +  Range (min … max):   153.0 ms … 173.4 ms    19 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Another good improvement. The last change I did is switching between
       +&lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting"&gt;&lt;code&gt;zsh-syntax-highlighting&lt;/code&gt;&lt;/a&gt;
       +to
       +&lt;a href="https://github.com/zdharma-continuum/fast-syntax-highlighting"&gt;&lt;code&gt;zsh-fast-syntax-highlighting&lt;/code&gt;&lt;/a&gt;,
       +that is supposed to be faster and have better highlighting too. I got that from
       +&lt;code&gt;_zsh_highlight_load_highlighters&lt;/code&gt; using 26% of the time from my &lt;code&gt;zprof&lt;/code&gt; above.
       +And for the final &lt;code&gt;hyperfine&lt;/code&gt; in my laptop:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     138.3 ms ±   7.1 ms    [User: 47.5 ms, System: 91.9 ms]
       +  Range (min … max):   123.8 ms … 157.9 ms    21 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;A ~36% improvement, not bad. Let's see how it fares in my Chromebook:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
       +Benchmark 1: zsh -ic exit
       +  Time (mean ± σ):     278.2 ms ±  46.9 ms    [User: 88.0 ms, System: 184.8 ms]
       +  Range (min … max):   204.7 ms … 368.5 ms    11 runs
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;An even more impressive ~59% improvement. And yes, the shell startup now feels
       +much better.&lt;/p&gt;</description><pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/troubleshoting-zsh-lag-and-solutions-with-nix/</guid></item><item><title>Quick bits: realise Nix symlinks</title><link>https://kokada.dev/blog/quick-bits-realise-nix-symlinks/</link><description>&lt;p&gt;When you are using Nix, especially with
       +&lt;a href="https://github.com/nix-community/home-manager/"&gt;Home-Manager&lt;/a&gt;, there are times
       +when you want to test something or maybe debug some issue in your
       +configuration. Those times it would be really convenient if you could avoid a
       +rebuild of your Home-Manager configuration, since this takes time until
       +evaluation and activation.&lt;/p&gt;
       +&lt;p&gt;For those times I have this small script in my Nix configuration called
       +&lt;code&gt;realise-symlinks&lt;/code&gt;, that is defined as:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;realise-symlink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeShellApplication&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;realise-symlink&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;runtimeInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;coreutils&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      for file in &amp;#34;$@&amp;#34;; do
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        if [[ -L &amp;#34;$file&amp;#34; ]]; then
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          if [[ -d &amp;#34;$file&amp;#34; ]]; then
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            tmpdir=&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;&amp;#39;&amp;#39;$&lt;/span&gt;&lt;span class="s1"&gt;{file}.tmp&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mkdir -p &amp;#34;$tmpdir&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            cp --verbose --recursive &amp;#34;$file&amp;#34;/* &amp;#34;$tmpdir&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            unlink &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mv &amp;#34;$tmpdir&amp;#34; &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            chmod --changes --recursive +w &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          else
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            cp --verbose --remove-destination &amp;#34;$(readlink &amp;#34;$file&amp;#34;)&amp;#34; &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            chmod --changes +w &amp;#34;$file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          fi
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        else
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          &amp;gt;&amp;amp;2 echo &amp;#34;Not a symlink: $file&amp;#34;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          exit 1
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        fi
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      done
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;realise-symlink&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea of this script is that you can call it against a symlink against Nix
       +store and it will realise, e.g.: convert to an &amp;quot;actual&amp;quot; file, e.g.:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 thiagoko users 69 Aug  1 00:10 .zshrc -&amp;gt; /nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; realise-symlink .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;removed &amp;#39;.zshrc&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;/nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zshrc&amp;#39; -&amp;gt; &amp;#39;.zshrc&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;.zshrc&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 5.8K Aug  1 00:16 .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It also add write permissions to the resulting file, to make it easier to edit.
       +By the way, it also works with directories:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 thiagoko users 90 Aug  1 00:10 zim-completion -&amp;gt; /nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zsh/plugins/zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; realise-symlink zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/init.zsh&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/init.zsh&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/init.zsh.zwc&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/init.zsh.zwc&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/LICENSE&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/LICENSE&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;zim-completion/README.md&amp;#39; -&amp;gt; &amp;#39;zim-completion.tmp/README.md&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/init.zsh&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/init.zsh.zwc&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/LICENSE&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;zim-completion/README.md&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&lt;/span&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah zim-completion
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;total 28K
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;drwxr-xr-x 1 thiagoko users   72 Aug  1 00:18 .
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;drwxr-xr-x 1 thiagoko users  130 Aug  1 00:18 ..
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 5.3K Aug  1 00:18 init.zsh
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users  12K Aug  1 00:18 init.zsh.zwc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 1.3K Aug  1 00:18 LICENSE
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rw-r--r-- 1 thiagoko users 2.2K Aug  1 00:18 README.md
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After you finish whatever you are testing, to return to your configuration you
       +can just delete those files and re-run your Home-Manager activation:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rm -rf .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemctl restart home-manager-&amp;lt;user&amp;gt;.service &lt;span class="c1"&gt;# or `home-manager switch`&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -lah .zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 thiagoko users 69 Aug  1 00:20 .zshrc -&amp;gt; /nix/store/glz018yyh0qfqc9lywx1yhr7c3l96lv7-home-manager-files/.zshrc
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It even works with system files (i.e.: NixOS):&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo realise-symlink /etc/nix/nix.conf
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[sudo] password for thiagoko:
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;removed &amp;#39;nix.conf&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;#39;/etc/static/nix/nix.conf&amp;#39; -&amp;gt; &amp;#39;nix.conf&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mode of &amp;#39;nix.conf&amp;#39; changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But I never needed for this case since it is more rare to me to experiment with
       +OS level configuration.&lt;/p&gt;</description><pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-realise-nix-symlinks/</guid></item><item><title>Generating YAML files with Nix</title><link>https://kokada.dev/blog/generating-yaml-files-with-nix/</link><description>&lt;p&gt;I hate YAML. Instead of writing an essay on why I hate YAML, I can just link to
       +&lt;a href="https://noyaml.com/"&gt;noyaml.com&lt;/a&gt;. In my personal projects I will never use it,
       +preferring either JSON, &lt;a href="https://toml.io/en/"&gt;TOML&lt;/a&gt; or even plain old
       +&lt;a href="https://en.wikipedia.org/wiki/INI_file"&gt;INI&lt;/a&gt; files depending on the use case.
       +However the ship has sailed already, there are tons of projects everywhere that
       +uses YAML: from most CI systems (&lt;a href="https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions"&gt;GitHub
       +Actions&lt;/a&gt;,
       +&lt;a href="https://circleci.com/docs/introduction-to-yaml-configurations/"&gt;CircleCI&lt;/a&gt;,
       +&lt;a href="https://docs.travis-ci.com/user/build-config-yaml"&gt;Travis&lt;/a&gt;, &lt;em&gt;et tu&lt;/em&gt;
       +&lt;a href="https://man.sr.ht/builds.sr.ht/"&gt;builds.sr.ht&lt;/a&gt;), to
       +&lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/"&gt;Kubernetes&lt;/a&gt;,
       +or in almost every
       +&lt;a href="https://guides.rubyonrails.org/configuring.html#configuring-a-database"&gt;Rails&lt;/a&gt;
       +application.&lt;/p&gt;
       +&lt;p&gt;One way to avoid at least some issues with the language is to write YAML in
       +another language. I will show my solution in one of my &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;personal
       +repositories&lt;/a&gt;, writing Nix to
       +generate GitHub Actions configuration files. Bonus points for validating the
       +result against the schema of GitHub Actions, so the famous &amp;quot;this is supposed to
       +be string instead of a list of strings&amp;quot; is gone.&lt;/p&gt;
       +&lt;p&gt;Let's start with the basics: YAML is supposed to be a &lt;a href="https://stackoverflow.com/a/1729545"&gt;superset of
       +JSON&lt;/a&gt;. What that means is that a JSON file
       +&lt;a href="https://yaml.org/spec/1.2-old/spec.html#id2759572"&gt;can be parsed&lt;/a&gt; by a YAML
       +parser. And Nix itself generates JSON natively, after all, Nix can be imagined
       +as &lt;a href="https://nix.dev/tutorials/nix-language.html"&gt;&amp;quot;JSON with functions&amp;quot;&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;To make things easier, I will assume that you have the &lt;code&gt;nix-commands&lt;/code&gt; and
       +&lt;code&gt;flakes&lt;/code&gt; enabled as &lt;code&gt;experimental-features&lt;/code&gt; in your Nix configuration. If not,
       +go &lt;a href="https://wiki.nixos.org/wiki/Flakes"&gt;here&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;Using the &lt;code&gt;nix eval&lt;/code&gt; command, we can generate a JSON expression from Nix by:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix &lt;span class="nb"&gt;eval&lt;/span&gt; --expr &lt;span class="s1"&gt;&amp;#39;{ foo = &amp;#34;bar&amp;#34;; }&amp;#39;&lt;/span&gt; --json
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;{&amp;#34;foo&amp;#34;:&amp;#34;bar&amp;#34;}
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, typing long excerpts of Nix code inside the console would be
       +impractical. We can write the following code inside a &lt;code&gt;foo.nix&lt;/code&gt; file instead:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix &lt;span class="nb"&gt;eval&lt;/span&gt; --file foo.nix --json
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;{&amp;#34;foo&amp;#34;:&amp;#34;bar&amp;#34;}
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While you can use a JSON output as an input for YAML parsers, it is probably
       +not the &lt;a href="https://metacpan.org/pod/JSON::XS#JSON-and-YAML"&gt;best idea&lt;/a&gt;. Sadly (or
       +maybe not), Nix has no native functionality to export data to YAML. However,
       +since we are using Nix, it is trivial to use &lt;code&gt;nixpkgs&lt;/code&gt; to use some program to
       +convert from JSON to YAML.&lt;/p&gt;
       +&lt;p&gt;To start, let's create a new directory, move our &lt;code&gt;foo.nix&lt;/code&gt; file to it, create a
       +new &lt;code&gt;flake.nix&lt;/code&gt; file and put the following contents:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Generate YAML files with Nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:NixOS/nixpkgs/nixos-unstable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x86_64-linux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;toYAML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;toYAML&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;yj&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;./go.nix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;passAsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;json&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;# will be available as `$jsonPath`&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mkdir -p $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            yj -jy &amp;lt; &amp;#34;$jsonPath&amp;#34; &amp;gt; $out/go.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are loading the &lt;code&gt;./foo.nix&lt;/code&gt; as a Nix file, converting it to JSON with
       +&lt;code&gt;builtins.toJSON&lt;/code&gt; function, and finally, using &lt;code&gt;pkgs.runCommand&lt;/code&gt; and its
       +&lt;code&gt;passAsFile&lt;/code&gt; option to load the contents of the JSON file into
       +&lt;a href="https://github.com/sclevine/yj"&gt;yj&lt;/a&gt;, that converts between serialisation
       +formats (&lt;code&gt;-jy&lt;/code&gt; flag means &amp;quot;JSON to YAML&amp;quot;). The reason I choose &lt;code&gt;yj&lt;/code&gt; is mostly
       +because it is a single binary Go program, but you can use whatever you prefer.&lt;/p&gt;
       +&lt;p&gt;By the way, there is a
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/9f918d616c5321ad374ae6cb5ea89c9e04bf3e58/lib/generators.nix#L805"&gt;&lt;code&gt;lib.generators.toYAML&lt;/code&gt;&lt;/a&gt;
       +inside &lt;code&gt;nixpkgs.lib&lt;/code&gt;, but as of the day of this post it only calls
       +&lt;code&gt;lib.strings.toJSON&lt;/code&gt; (that in turn, calls &lt;code&gt;builtins.toJSON&lt;/code&gt;). So it doesn't
       +really help here. Another option would be &lt;code&gt;pkgs.formats.yaml.generate&lt;/code&gt;, that
       +converts between formats, but it calls
       +&lt;a href="https://github.com/NixOS/nixpkgs/blob/008ceae1a2b47a84d7aa01e55f8468272c70b9ee/pkgs/pkgs-lib/formats.nix#L77-L84"&gt;&lt;code&gt;remarshal&lt;/code&gt;&lt;/a&gt;
       +(in Python), so not my favorite choice.&lt;/p&gt;
       +&lt;p&gt;If we run the following commands, we can see the result:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; nix build .#packages.x86_64-linux.toYAML
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cat result/foo.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo: bar
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That is the basic idea. To have a more realistic example, let's convert the
       +&lt;a href="https://github.com/thiagokokada/blog/blob/4e3f25485c6682f3e066b219df2290934bc0d256/.github/workflows/go.yml"&gt;&lt;code&gt;go.yml&lt;/code&gt;&lt;/a&gt;,
       +that builds this blog, to Nix:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;branches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;main&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;runs-on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ubuntu-latest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;actions/checkout@v4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Set up Go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;actions/checkout@v4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;with&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;go-version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.21&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;make&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Publish&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;make publish&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MATAROA_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;&amp;#39;&amp;#39;$&lt;/span&gt;&lt;span class="s1"&gt;{{ secrets.MATAROA_TOKEN }}&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Commit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stefanzweifel/git-auto-commit-action@v5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;with&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;README/rss:update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some interesting things to highlight: &lt;code&gt;with&lt;/code&gt; is a reserved word in Nix, so we
       +need to quote it. Not a problem, but something to be aware. And the template
       +string in GitHub Actions uses the same &lt;code&gt;${}&lt;/code&gt; that Nix uses, so we need to
       +escape.&lt;/p&gt;
       +&lt;p&gt;And after running the following commands:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ nix build .#packages.x86_64-linux.toYAML
       +$ cat result/go.yaml
       +jobs:
       +  build:
       +    permissions:
       +      contents: write
       +    runs-on: ubuntu-latest
       +    steps:
       +      - uses: actions/checkout@v4
       +      - name: Set up Go
       +        uses: actions/checkout@v4
       +        with:
       +          go-version: &amp;quot;1.21&amp;quot;
       +      - name: Update
       +        run: make
       +      - env:
       +          MATAROA_TOKEN: ${{ secrets.MATAROA_TOKEN }}
       +        name: Publish
       +        run: make publish
       +      - name: Commit
       +        uses: stefanzweifel/git-auto-commit-action@v5
       +        with:
       +          commit_message: README/rss:update
       +name: Go
       +&amp;quot;on&amp;quot;:
       +  push:
       +    branches:
       +      - main
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Yes, the keys are not in the same order as we defined, since Nix, like most
       +programming languages (with the exception of
       +&lt;a href="https://mail.python.org/pipermail/python-dev/2017-December/151283.html"&gt;Python&lt;/a&gt;),
       +do not guarantee the insertion order in maps/dicts/attrsets/whatever. But I
       +really hope whatever is consuming your YAML is not relying in the order the
       +keys are defined (this would be more cursed than YAML already is).&lt;/p&gt;
       +&lt;p&gt;So that is basically it. For the bonus points that I talked at the start of the
       +post, we can modify &lt;code&gt;pkgs.runCommand&lt;/code&gt; to run some kind of validator. I use
       +&lt;a href="https://github.com/mpalmer/action-validator"&gt;&lt;code&gt;action-validator&lt;/code&gt;&lt;/a&gt;, one that I
       +particularly packaged in
       +&lt;a href="https://github.com/NixOS/nixpkgs/pull/260217"&gt;nixpkgs&lt;/a&gt; to use in those cases.
       +But you could use e.g.: a validator of Kubernetes YAML. Or a generic YAML lint
       +like this &lt;a href="https://github.com/adrienverge/yamllint"&gt;one&lt;/a&gt;. The possibilities are
       +endless.&lt;/p&gt;
       +&lt;p&gt;Let's modify our &lt;code&gt;flake.nix&lt;/code&gt; to add the validation:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x86_64-linux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;toYAML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runCommand&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;toYAML&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;action-validator&lt;/span&gt; &lt;span class="n"&gt;yj&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="sr"&gt;./go.nix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;passAsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;json&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            mkdir -p $out
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            yj -jy &amp;lt; &amp;#34;$jsonPath&amp;#34; &amp;gt; $out/go.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;            action-validator -v $out/go.yaml
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;          &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And let's add an error in our &lt;code&gt;go.nix&lt;/code&gt; file:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/go.nix b/go.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 25e0596..8c00033 100644
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;&lt;/span&gt;&lt;span class="gd"&gt;--- a/go.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;&lt;/span&gt;&lt;span class="gi"&gt;+++ b/go.nix
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -5,7 +5,7 @@
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;&lt;/span&gt;   jobs = {
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     build = {
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       runs-on = &amp;#34;ubuntu-latest&amp;#34;;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-      permissions.contents = &amp;#34;write&amp;#34;;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;&lt;/span&gt;&lt;span class="gi"&gt;+      permissions.contents = [ &amp;#34;write&amp;#34; ];
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;&lt;/span&gt;       steps = [
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         { uses = &amp;#34;actions/checkout@v4&amp;#34;; }
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         {
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, let's try to build our YAML file again:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;$ nix build .#packages.x86_64-linux.toYAML
       +error: builder for '/nix/store/j8wr6j1pvyf986sf74hqw8k31lvlzac5-toYAML.drv' failed with exit code 1;
       +       last 25 log lines:
       +       &amp;gt;                                 &amp;quot;Additional property 'runs-on' is not allowed&amp;quot;,
       +       &amp;gt;                             ),
       +       &amp;gt;                             path: &amp;quot;/jobs/build&amp;quot;,
       +       &amp;gt;                             title: &amp;quot;Property conditions are not met&amp;quot;,
       +       &amp;gt;                         },
       +       &amp;gt;                         Properties {
       +       &amp;gt;                             code: &amp;quot;properties&amp;quot;,
       +       &amp;gt;                             detail: Some(
       +       &amp;gt;                                 &amp;quot;Additional property 'steps' is not allowed&amp;quot;,
       +       &amp;gt;                             ),
       +       &amp;gt;                             path: &amp;quot;/jobs/build&amp;quot;,
       +       &amp;gt;                             title: &amp;quot;Property conditions are not met&amp;quot;,
       +       &amp;gt;                         },
       +       &amp;gt;                         Required {
       +       &amp;gt;                             code: &amp;quot;required&amp;quot;,
       +       &amp;gt;                             detail: None,
       +       &amp;gt;                             path: &amp;quot;/jobs/build/uses&amp;quot;,
       +       &amp;gt;                             title: &amp;quot;This property is required&amp;quot;,
       +       &amp;gt;                         },
       +       &amp;gt;                     ],
       +       &amp;gt;                 },
       +       &amp;gt;             ],
       +       &amp;gt;         },
       +       &amp;gt;     ],
       +       &amp;gt; }
       +       For full logs, run 'nix log /nix/store/j8wr6j1pvyf986sf74hqw8k31lvlzac5-toYAML.drv'.
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Yes, the output of &lt;code&gt;action-validator&lt;/code&gt; is awfully verbose, but it is still
       +better than making &lt;a href="https://x.com/eric_sink/status/1430954572848287744"&gt;&amp;quot;8 commits/push in one
       +hour&amp;quot;&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;If you are interested in how a more advantage usage of this technique is,
       +including usage of functions and constants to share common steps between
       +different actions, please take a look at the
       +&lt;a href="https://github.com/thiagokokada/nix-configs/tree/master/actions"&gt;actions&lt;/a&gt;
       +(&lt;a href="https://github.com/thiagokokada/nix-configs/tree/fbbc8f935cc7f774f691196e1612bbf7dd2054b5/actions"&gt;permalink&lt;/a&gt;)
       +in my &lt;code&gt;nix-config&lt;/code&gt; repository.&lt;/p&gt;</description><pubDate>Wed, 31 Jul 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/generating-yaml-files-with-nix/</guid></item><item><title>First impressions: FPGBC</title><link>https://kokada.dev/blog/first-impressions-fpgbc/</link><description>&lt;p&gt;Here is something for nostalgia: I just put together a &lt;a href="https://en.wikipedia.org/wiki/Game_Boy_Color"&gt;Game Boy
       +Color&lt;/a&gt; made of completely new
       +parts for a friend: here is the
       +&lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;FPGBC&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;The &lt;em&gt;FP&lt;/em&gt; part of the name comes from
       +&lt;a href="https://en.wikipedia.org/wiki/Field-programmable_gate_array"&gt;FPGA&lt;/a&gt;, because
       +instead of software emulation this device use FPGA to reproduce the device.
       +While I am not convinced that FPGA is necessary more accurate than a good
       +software emulator, one advantage of FPGA is the (possible) lower input latency
       +thanks to the avoidance of complexity to handle the user input (e.g.: the
       +Operational System). A quick playthrough against &lt;a href="https://en.wikipedia.org/wiki/Motocross_Maniacs"&gt;Motocross
       +Maniacs&lt;/a&gt; seems to be fine, but
       +I can't see much difference from my &lt;a href="https://retrogamecorps.com/2022/05/15/miyoo-mini-v2-guide/"&gt;Miyoo
       +Mini+&lt;/a&gt; (I will do
       +more comparisons between the two devices later), that is a software emulation
       +device.&lt;/p&gt;
       +&lt;p&gt;But I think focusing in accuracy is wrong, the main reason of getting a device
       +like this one is for nostalgia, and this definitely hit the mark. The quality
       +of the case is as good as I remember the original, and most of the details are
       +replicate perfectly, including reproduction stickers in the back of the device.
       +The only differences that I can find is the usage of USB-C port for charging in
       +place of the barrel jack power adapter (thanks!), and the fact that the screen
       +bezels are smaller compared to the original (because the screen is bigger) and
       +doesn't include the Game Boy Color logo (that is fine in my opinion, since it
       +would look weird in the fine bezels). It even has a supposedly working &lt;a href="https://en.wikipedia.org/wiki/Game_Link_Cable"&gt;Link
       +Cable&lt;/a&gt; (I don't have another
       +Game Boy to test). Sadly it is missing the infrared sensor, but the usage of
       +that was pretty limited anyway.&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175245569.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175245569.jpg" alt="FPGBC running Tetris."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175131157.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_175131157.jpg" alt="Back of FPGBC. It includes even reproduction stickers of the original."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;So how well does it work? I can't say for sure. I don't have any original games
       +with me, so I am relying in backups and a
       +&lt;a href="https://en.wikipedia.org/wiki/Flashcard"&gt;flashcard&lt;/a&gt; for now. Many games that I
       +tested works fine, a few of them have graphical issues that can be fixed in the
       +menu (more about it later), and some of them doesn't boot. But I don't know if
       +the issue with the games not booting are because of the roms, the flashcard or
       +the device itself.&lt;/p&gt;
       +&lt;p&gt;By the way, the flashcard I am using is a cheap knockoff of an &lt;a href="https://gbatemp.net/review/everdrive-gb.141/"&gt;Everdrive
       +GB&lt;/a&gt;. This FPGBC came with
       +firmware v1.09, while there is an update available for v1.10 in the
       +&lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;website&lt;/a&gt;. I had an weird issue in
       +the new firmware where no games would boot with this knockoff Everdrive so I
       +had to go back to v1.09, but again, I am not sure if the issue was fact that I
       +am using a knockoff device or this would happen with an original Everdrive GB.
       +If you are going to buy a proper Everdrive, you probably wouldn't get a
       +Everdrive GB anyway since it is discontinued, and it seems the &lt;a href="https://www.reddit.com/r/Gameboy/comments/1atwjh3/fpgbc_everdrive_compatibility/"&gt;newer
       +versions&lt;/a&gt;
       +have better compatibility with FPGBC.&lt;/p&gt;
       +&lt;p&gt;Sadly that the update didn't work, since there is this
       +&lt;a href="https://github.com/makhowastaken/GWGBC_FW"&gt;repository&lt;/a&gt; that patches the
       +firmware to boot the original logo instead of the ugly FPGBC one. And yes, for
       +some reason the v1.09 firmware from this repository still doesn't work with my
       +knockoff Everdrive.&lt;/p&gt;
       +&lt;p&gt;By the way, it seems the device is not easy to brick: I borked the firmware
       +update process once while trying to downgrade back to v1.09, resulting in a
       +black screen when I turned on the console. But just connecting the device to
       +the computer and powering on, I could flash the firmware again and the device
       +came back to life.&lt;/p&gt;
       +&lt;p&gt;About the features of the device: if you press the volume button (yes, you can
       +press it now), it opens the following menu:&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_210604830.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_210604830.jpg" alt="FPGBC menu."&gt;&lt;/a&gt;&lt;/p&gt;
       +&lt;p&gt;The first 2 options are the LCD backlight (&lt;code&gt;BKLT&lt;/code&gt;) and volume (&lt;code&gt;VOL&lt;/code&gt;). I didn't talk about
       +those, but the LCD screen seems to be IPS, and the quality is really good, and
       +also looks bright enough to play even under bad lightining conditions. And the
       +speaker has good quality, the sound is better than I remember, but sadly the
       +maximum volume is kind low. Still should be enough for playing in a quiet room.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;DISPMOD&lt;/code&gt; is probably the most controversial option: it allow you to set which
       +scale you want. Anything with &lt;code&gt;EMU&lt;/code&gt; at the end means emulating the original
       +colors, and as far I remember it gets really close. You can also chose betwen
       +&lt;code&gt;X4&lt;/code&gt;, &lt;code&gt;X4P&lt;/code&gt; and &lt;code&gt;FUL&lt;/code&gt;, the last one is the one shown in the photos where the
       +image fills the whole screen at the cost of non-integer scaling. &lt;code&gt;X4&lt;/code&gt; is
       +integer scaling, however the image doesn't fill the whole screen. The &lt;code&gt;X4P&lt;/code&gt;
       +also includes a pixel effect that makes the image closer than the original
       +screen. It actually looks good, but the fact that I chose a white border for
       +this FPGBC makes the border really distracting. Maybe the black one is a better
       +choice if you want integer scale.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;CORE&lt;/code&gt; is simple: you can choose between &lt;code&gt;GB&lt;/code&gt; (Game Boy) or &lt;code&gt;GBC&lt;/code&gt; (Game Boy
       +Color). For those who don't know, you can run Game Boy games in Game Boy Color
       +and they will be automatically colorised. Some people don't like this and
       +prefer the colors of &lt;code&gt;GB&lt;/code&gt;, so you have this option. The &lt;code&gt;GB_PALETTE&lt;/code&gt; allows you
       +to chose the color in GB mode, for example, the green-ish colors from the
       +original Game Boy or the blue-ish colors from &lt;a href="https://nintendo.fandom.com/wiki/Game_Boy_Light"&gt;Game Boy
       +Light&lt;/a&gt;. And yes, you can
       +choose the color palette for Game Boy games running in &lt;code&gt;GBC&lt;/code&gt; mode by pressing a
       +&lt;a href="https://gbstudiocentral.com/tips/game-boy-color-modes/"&gt;button combination&lt;/a&gt; at
       +the boot screen, but it seems not working in my unit and again, not sure if the
       +fault is my knockoff Everdrive.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;FRAME_MIX&lt;/code&gt; basically is an option that makes some effects, like transparency
       +in &lt;a href="https://en.wikipedia.org/wiki/Wave_Race"&gt;Wave Race&lt;/a&gt;, to work at the cost of
       +introducing blurriness. The reason for this is that those effects depends in
       +the fact that the Game Boy screen was slow refresh, so you could rely on it by
       +rapidly changing pixels to create some interesting effects, but sadly those
       +effects doesn't work well in modern displays.&lt;/p&gt;
       +&lt;p&gt;&lt;code&gt;GB_CLRFIX&lt;/code&gt; is the option I mentioned before, where some Game Boy games just
       +get completely wrong colors for some reason, e.g.: &lt;a href="https://en.wikipedia.org/wiki/The_Addams_Family_(video_game)"&gt;The Addams
       +Family&lt;/a&gt;. Turning
       +on fixes those games, but I am not sure if this option breaks other games.&lt;/p&gt;
       +&lt;p&gt;Finally, &lt;code&gt;SPD&lt;/code&gt; allows you to increase or decrease the CPU clock, slowing or
       +speeding up the games (including the sound). The result can be hilarious, so I
       +think this is a nice addition to the features. Sadly you can't know what the
       +default speed is, so you need to rely on sound to adjust back to the default.&lt;/p&gt;
       +&lt;p&gt;So in the end, can I recommend a FPGBC? I am not sure. If you want a device to
       +play games, I still think something like a Miyoo Mini+ is a better choice. Not
       +only you will have access to more games from different platforms, you also
       +don't need to rely on flashcards or cartridges. Also it has way more features
       +than FPGBC, like wireless multiplayer,
       +&lt;a href="https://retroachievements.org/"&gt;RetroArchivements&lt;/a&gt; and save states.&lt;/p&gt;
       +&lt;p&gt;But the actual reason to get a FPGBC is nostalgia, and for that I think the
       +FPGBC is difficult to beat. The price of the &lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;kit to
       +assemble&lt;/a&gt; ($69.90) is cheaper than
       +most Game Boy's in good condition you can find in eBay, and you get for that
       +price a rechargable battery, an amazing quality screen, the PCB and the
       +speaker. You need to buy separately the case and the buttons, but in total you
       +will still end up paying less, and allows you to fully customise your build.
       +And the result device is not only in mint condition, it is really convenient
       +too: recharging batteries (via USB-C even) is much more convenient than buying
       +AA batteries, and the screen not only is better but it even has backlight. You
       +can also buy a fully built console for
       +&lt;a href="https://funnyplaying.com/products/fpgbc-console"&gt;$99.00&lt;/a&gt;, but you have less
       +options of customisation.&lt;/p&gt;
       +&lt;p&gt;This is the classic case of do what I say, don't do what I do. This FPGBC is a
       +gift, and I will buy another one soon. Can't wait to play &lt;a href="https://en.wikipedia.org/wiki/Pok%C3%A9mon_Gold_and_Silver"&gt;Pokémon
       +Gold&lt;/a&gt; in (almost)
       +original hardware again.&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_123847458.jpg"&gt;&lt;img src="https://github.com/thiagokokada/blog/raw/main/posts/2024-07-30/PXL_20240729_123847458.jpg" alt="The kit before assemble."&gt;&lt;/a&gt;&lt;/p&gt;</description><pubDate>Tue, 30 Jul 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/first-impressions-fpgbc/</guid></item><item><title>Quick bits: why you should automate everything</title><link>https://kokada.dev/blog/quick-bits-why-you-should-automate-everything/</link><description>&lt;p&gt;If everything works as expected this blog post should appear in &lt;a href="https://kokada.capivaras.dev/"&gt;in my
       +blog&lt;/a&gt; without I ever touching the
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; website. I rewrote my &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;previous Python
       +script&lt;/a&gt; to Go
       +(&lt;a href="https://github.com/thiagokokada/blog/blob/3c39e0f7cd58b1af885f69871490b05bf6fc7d99/blog.go"&gt;permalink&lt;/a&gt;)
       +since my attempt to generate proper description to the RSS feed resulted in
       +slow startup times (not because of Python, but because of my usage of
       +&lt;code&gt;nix-shell&lt;/code&gt; since I didn't want to deal with
       +&lt;a href="https://docs.python.org/3/library/venv.html"&gt;venv&lt;/a&gt; or anything to manage my
       +Python dependencies).&lt;/p&gt;
       +&lt;p&gt;My previous workflow of this blog already involved me writing the texts in
       +&lt;a href="https://neovim.io/"&gt;neovim&lt;/a&gt;, copying and pasting the result in the
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; website and publishing. This was not
       +that bad, except that it seems I have a heavy tendency of editing my posts
       +multiple times. Copying and pasting data between neovim and the website became
       +tedious, so I decided to give up and automate the whole process.&lt;/p&gt;
       +&lt;p&gt;&lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt; (the blog platform
       +&lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; run) has a reasonable good
       +&lt;a href="https://mataroa.blog/api/docs/"&gt;API&lt;/a&gt;, and it only took a few hours to get a
       +version of publishing working (it would take less if
       +&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, the framework Mataroa is written, did
       +not have a weird behavior with URLs missing a trailing &lt;code&gt;/&lt;/code&gt;). An additional few
       +lines of
       +&lt;a href="https://github.com/thiagokokada/blog/blob/51b20612335c7f4312a51a0f436235b4b701ce8b/.github/workflows/go.yml"&gt;YAML&lt;/a&gt;
       +to make GitHub Actions trigger a pipeline and now I should never have to
       +manually update my blog again.&lt;/p&gt;
       +&lt;p&gt;I could have not done this. I mean, I probably wasted more time writing an
       +automation than I actually wasted publishing manually. But the manual process
       +is easy to miss, and I already did multiple mistakes publishing in the manual
       +method. For example, when writing the Markdown files, each post is written in a
       +particular format, where the first header is considered the title, so I need to
       +remove it from the contents during publication. But of course, this is easy to
       +miss, and I had to fix this multiple times already.&lt;/p&gt;
       +&lt;p&gt;So yes, I think this is a good lesson on why you should automate everything. It
       +is more than just about &lt;a href="https://xkcd.com/1205/"&gt;time savings&lt;/a&gt;, it is about
       +reducing mistakes and even documenting (even if poorly) a process. I mean, the
       +code I wrote is not that great, but I can definitely rely on it in the future
       +to remember what I need to do. It will be much faster than trying to learn from
       +scratch again.&lt;/p&gt;</description><pubDate>Mon, 29 Jul 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-why-you-should-automate-everything/</guid></item><item><title>Go, a reasonable good language</title><link>https://kokada.dev/blog/go-a-reasonable-good-language/</link><description>&lt;p&gt;Go was one of the languages that I always was interested to learn, but never
       +got the hang of it. I first got interested in the language when I was in my
       +first job, between 2016-2018. At the time the language was a completely
       +different beast: no modules, no generics, no easy way to error wrap yet, etc.&lt;/p&gt;
       +&lt;p&gt;Go forward 2023 (no pun indented), I wrote my &lt;a href="https://github.com/thiagokokada/twenty-twenty-twenty/"&gt;first project in
       +Go&lt;/a&gt;, wrote some scripts
       +at &lt;code&gt;$CURRENT_JOB&lt;/code&gt; in the language, and now wrote &lt;a href="https://github.com/thiagokokada/hyprland-go/"&gt;my first
       +library&lt;/a&gt; (with an &lt;a href="https://kokada.dev/blog/writing-nixos-tests-for-fun-and-profit"&gt;interesting
       +way to run
       +CI&lt;/a&gt;). I am also
       +writing more
       +&lt;a href="https://github.com/thiagokokada/nix-configs/blob/8c559527ed12e1d4f57a3fc5c72630b956f4c290/home-manager/desktop/wayland/hyprland/hyprtabs/hyprtabs.go"&gt;scripts&lt;/a&gt;
       +in the language, where I would prefer to use Bash or Python before. Heck, even
       +this blog is automatically published with a &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;Go
       +script&lt;/a&gt;,
       +that used to be a &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;Python
       +one&lt;/a&gt; before. I can
       +say that nowadays it is another language in my toolbox, and while it is still a
       +love and hate relationship, recently it is more about love and less about hate.&lt;/p&gt;
       +&lt;p&gt;The points that I love about Go is probably obvious for some, but still
       +interesting to talk about anyway. The fact that the language generates static
       +binaries by default and have fast compilation times is something that I
       +apreciate since I first heard about the language, and now that I am using the
       +language frequently, are points thatI appreciate even more. Something about
       +getting almost instant feedback after changing a line of code and running &lt;code&gt;go run&lt;/code&gt; (even with its quirks) are great for the developer experience. This is the
       +main reason why I am using the language more frequently for scripts.&lt;/p&gt;
       +&lt;p&gt;Then we have the fast startup times. I am kind of sensitive to latency,
       +especially of command line utilities that need to answer fast when I expect
       +them to be fast (e.g.: &lt;code&gt;foo --help&lt;/code&gt;). This is one part where I could have
       +issues in Python, especially for more complex programs, but in Go it is rarely
       +an issue.&lt;/p&gt;
       +&lt;p&gt;Modules are also fantastic. It is not without its weirdness (like everything in
       +Go ecossystem), but the fact that it is so easy to add and manage dependencies
       +in a project using only the &lt;code&gt;go&lt;/code&gt; CLI is great. I also like that it generates a
       +hash of every dependency, making it reproducible (well, probably not at Nix
       +level, but still reproducible).&lt;/p&gt;
       +&lt;p&gt;Since I started to talk about &lt;code&gt;go&lt;/code&gt; CLI, what a great tool! The fact that you
       +can manage dependencies, generate documentation, format code, lint, run
       +tests/&lt;a href="https://blog.logrocket.com/benchmarking-golang-improve-function-performance/"&gt;benchmarks&lt;/a&gt;/&lt;a href="https://go.dev/doc/security/fuzz/"&gt;fuzzing&lt;/a&gt;,
       +check code for &lt;a href="https://go.dev/doc/articles/race_detector"&gt;races&lt;/a&gt; etc., all
       +with just the &amp;quot;compiler&amp;quot; for the language is excelent. Still probably one of
       +the best developer experiences I know in any programming language.&lt;/p&gt;
       +&lt;p&gt;I will not even talk about the things that everyone talks about Go, like
       +&lt;a href="https://go.dev/doc/effective_go#goroutines"&gt;goroutines&lt;/a&gt;, because I just don't
       +think I can add anything interesting to the topic.&lt;/p&gt;
       +&lt;p&gt;Now for the parts that I like less, the test part still quirks me that it is
       +not based in assertions, but thankfully it is easy to write assertions with
       +generics nowadays:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="nx"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;want&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;want&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;got: %#v, want: %#v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;want&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;GreaterOrEqual&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="nx"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ordered&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;got: %v; want: &amp;gt;=%v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// etc...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just one of those things that I end up re-writing in every project. Yes, I know
       +about &lt;a href="https://github.com/stretchr/testify"&gt;testify&lt;/a&gt; and other assertion
       +libraries, but quoting &lt;a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;amp;t=568s"&gt;Rob
       +Pike&lt;/a&gt; here, &amp;quot;a little
       +copying is better than a little dependency&amp;quot;. As long the code you write is
       +trivial, it is better to duplicate the code than try to import a dependency.&lt;/p&gt;
       +&lt;p&gt;About another piece of code that generics allows me to write and I always end
       +up re-writing in every project is the &lt;code&gt;must*&lt;/code&gt; family of functions:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;must1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nf"&gt;must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nx"&gt;must2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T1&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T2&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt; &lt;span class="nx"&gt;T1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v2&lt;/span&gt; &lt;span class="nx"&gt;T2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nf"&gt;must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v2&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// must3, must4, etc...
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Those functions are so useful, especially for scripts where I generally don't
       +want to handle each error: if I have an error, I want the program to halt and
       +print a stack trace (exactly as I would have with a language with exceptions).
       +It basically allow me to convert code from:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nf"&gt;must1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This brings Go closer to Python to me, and I think for scripts this is
       +something great.&lt;/p&gt;
       +&lt;p&gt;Finally, for the things that I hate, well the biggest one currently is the lack
       +of nullability (or in Go terms,
       +&lt;a href="https://github.com/golang/go/issues/49202"&gt;nillability&lt;/a&gt;). After using
       +languages that has it, like Kotlin, or even something like
       +&lt;a href="https://www.mypy-lang.org/"&gt;mypy&lt;/a&gt;, this is one of those things that completely
       +changes the developer experience. I also still don't like the error handling
       +(but &lt;code&gt;must*&lt;/code&gt; goes far by improving the situation, when it is possible to use
       +it), especially because it is easy to lose context on it:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// bad
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readFileContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// good
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;readFileContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;readFileContents: error while reading a file: %w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I also have some grips about the mutate everything approach of the language. I
       +prefer immutability by default, but I find that in general as long as you split
       +your functions at a reasonable size it is generally fine.&lt;/p&gt;
       +&lt;p&gt;And for a language that is supposed to be straightforward, it is strange on how
       +much magic the language relies on, in the form of things like &lt;a href="https://go.dev/doc/modules/layout"&gt;&lt;code&gt;internal&lt;/code&gt; and
       +&lt;code&gt;main&lt;/code&gt;&lt;/a&gt; packages, name capitalisation to
       +indicate visibility (&lt;code&gt;private&lt;/code&gt; vs &lt;code&gt;Public&lt;/code&gt;), conditional compiling by filenames
       +(e.g.: &lt;code&gt;foo_amd64.go&lt;/code&gt;, &lt;code&gt;bar_linux.go&lt;/code&gt;), magic comments (e.g.: &lt;code&gt;//go:build&lt;/code&gt;),
       +etc.&lt;/p&gt;
       +&lt;p&gt;I expect to write more Go code going forward. Not because it is the perfect
       +language or whatever, but just because it a is language that has some really
       +good qualities that makes the language attractive even with the issues that I
       +have. That makes it a reasonable good language, and at least for me this is
       +good enough.&lt;/p&gt;</description><pubDate>Mon, 29 Jul 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/go-a-reasonable-good-language/</guid></item><item><title>Quick bits: nix-shell is cursed</title><link>https://kokada.dev/blog/quick-bits-nix-shell-is-cursed/</link><description>&lt;p&gt;The other day I had to run a PHP project in my machine. I have no idea how PHP
       +ecosystem work, I just wanted to get it to run.&lt;/p&gt;
       +&lt;p&gt;The easiest way to get a script to run if you use Nix is to use &lt;code&gt;nix-shell&lt;/code&gt;. As
       +many of you probably know, you can add &lt;code&gt;nix-shell&lt;/code&gt; as a shebang in your scripts
       +to run them as &lt;code&gt;./script&lt;/code&gt;. This was a PHP script so I wanted to do the same.
       +Easy right?&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!/use/bin/env nix-shell
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!nix-shell -i php -p php83
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strict_types&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;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./index.php
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Fatal error: strict_types declaration must be the very first statement in the script in index.php on line 4
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So it seems that &lt;code&gt;declare(strict_types=1)&lt;/code&gt; needs to be the first line in a PHP
       +script if used. I removed &lt;code&gt;declare(strict_types=1)&lt;/code&gt; and while the script works,
       +I don't have enough expertise in PHP to know if this would be safe or not.&lt;/p&gt;
       +&lt;p&gt;I decided to try something that initially looked really dumb:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!/use/bin/env nix-shell
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strict_types&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;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!nix-shell -i php -p php83
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./index.php
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Works
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wat? I mean, it is not dumb if it works, but this at least looks cursed.&lt;/p&gt;
       +&lt;p&gt;Eventually I found this
       +&lt;a href="https://github.com/NixOS/nix/issues/2570#issuecomment-446220517"&gt;comment&lt;/a&gt; in a
       +Nix issue talking about cases where &lt;code&gt;nix-shell&lt;/code&gt; shebang doesn't work. It looks
       +like the classic case of a &lt;a href="https://github.com/NixOS/nix/issues/2570#issuecomment-446222206"&gt;bug that becomes a
       +feature&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; after posting this in
       +&lt;a href="https://lobste.rs/s/gkcgza/quick_bits_nix_shell_is_cursed"&gt;Lobte.rs&lt;/a&gt;, it seems
       +someone decided to open a &lt;a href="https://github.com/NixOS/nix/pull/11202"&gt;Pull
       +Request&lt;/a&gt; to document this behavior.
       +Also the equivalent for the new &lt;a href="https://nix.dev/manual/nix/2.23/command-ref/new-cli/nix#shebang-interpreter"&gt;nix
       +CLI&lt;/a&gt;
       +explicitly documents this behavior:&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;Note that the &lt;code&gt;#! nix&lt;/code&gt; lines don't need to follow after the first line, to
       +accomodate other interpreters.&lt;/p&gt;
       +&lt;/blockquote&gt;</description><pubDate>Sat, 27 Jul 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/quick-bits-nix-shell-is-cursed/</guid></item><item><title>Using GitHub as a (bad) blog platform</title><link>https://kokada.dev/blog/using-github-as-a-bad-blog-platform/</link><description>&lt;p&gt;I finally started a new blog, thanks to the offer of
       +&lt;a href="https://gluer.org/"&gt;@ratsclub&lt;/a&gt; to give me free access to
       +&lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt;. But considering how small this blog
       +platform is supposed to be, I want to have at least somewhere to have a backup
       +of my posts. I know &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt;, the blog platform that
       +&lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt; runs, has automatic e-mail backups, but
       +I want something more reliable.&lt;/p&gt;
       +&lt;p&gt;I am writing all my posts in Markdown (the format that &lt;a href="https://1.mataroa.blog/guides/markdown/"&gt;Mataroa
       +supports&lt;/a&gt;) files inside
       +&lt;a href="https://neovim.io/"&gt;neovim&lt;/a&gt; anyway, so why not store all my Markdown files in
       +Git? So this is what I did, I now have an unofficial mirror in
       +&lt;a href="https://github.com/thiagokokada/blog"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;While I am here, why not overcomplicate? Can I make an usable blog platform
       +from GitHub? And by that I don't mean GitHub pages, the repository itself. I
       +mean, it already renders Markdown files by default, so no need to do anything
       +in that space. To reach feature parity with
       +&lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt;, I only need to have an index and RSS
       +(since comments are not supported anyway). No need for newsletter since GitHub
       +has a &lt;a href="https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/managing-subscriptions-for-activity-on-github/viewing-your-subscriptions"&gt;watch
       +feature&lt;/a&gt;
       +already.&lt;/p&gt;
       +&lt;p&gt;After a couple of hours hacking a Python script, you can see the result of this
       +monstrosity &lt;a href="https://github.com/thiagokokada/blog"&gt;here&lt;/a&gt;. The script, called
       +&lt;code&gt;gen_blog.py&lt;/code&gt;, is available at the same repository (here is a
       +&lt;a href="https://github.com/thiagokokada/blog/blob/c8986d1ab1b94c0986fd814629bb8eb4034fb6e7/gen_blog.py"&gt;permalink&lt;/a&gt;).
       +It automatically generates an index at
       +&lt;a href="https://github.com/thiagokokada/blog/blob/main/README.md"&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; with
       +each blog post and a
       +&lt;a href="https://raw.githubusercontent.com/thiagokokada/blog/main/rss.xml"&gt;&lt;code&gt;rss.xml&lt;/code&gt;&lt;/a&gt;
       +file at the root of the repository.&lt;/p&gt;
       +&lt;p&gt;Instead of trying to explain the code, I am going to explain the general idea,
       +because I think that if you want to replicate this idea it is better to rewrite
       +it in a way that you understand. It shouldn't take more than 2 hours in any
       +decent programming language. But if you really want, the script itself is
       +licensed in &lt;a href="https://en.wikipedia.org/wiki/WTFPL"&gt;WTFPL&lt;/a&gt; license. The code only
       +uses Python 3's standard library and should work in any relatively recent
       +version (anything newer than 3.9 should work).&lt;/p&gt;
       +&lt;p&gt;So the idea is basically to organise the repository and the Markdown files in a
       +easy way that makes it trivial to parse in a deterministic way. For example, my
       +repository is organised in the following way:&lt;/p&gt;
       +&lt;pre&gt;&lt;code&gt;.
       +├── 2024-07-26
       +│   ├── 01-writing-nixos-tests-for-fun-and-profit.md
       +│   └── 02-using-github-as-a-bad-blog-platform.md &amp;lt;- this file
       +├── gen_blog.py
       +├── README.md
       +└── rss.xml
       +&lt;/code&gt;&lt;/pre&gt;
       +&lt;p&gt;Each day that you write a new blog post will be on its own directory. This is
       +nice because Markdown files may include extra files than the posts themselves,
       +e.g.: images, and this organisation make it trivial to organise everything.&lt;/p&gt;
       +&lt;p&gt;Each post has its own Markdown file. I put a two digit number before each post,
       +to ensure that when publishing multiple posts at the same day I keep them in
       +the same order of publishing. But if you don't care about it, you can just name
       +the files whatever you want.&lt;/p&gt;
       +&lt;p&gt;Also, I am assuming that each Markdown file has a header starting with &lt;code&gt;# &lt;/code&gt;,
       +and that is the title of the blog post.&lt;/p&gt;
       +&lt;p&gt;Using the above organisation, I have this function that scrapes the repository
       +and collect the necessary information to generate the index and RSS files:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;grab_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pwd&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pwd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Ignore non-directories or hidden files&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Try to parse date from directory name&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="nb"&gt;print&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;#34;WARN: ignoring non-date directory: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# Iterate between the files in the date directory&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# Ignore non-markdown files or hidden files (draft)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.md&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# Grab the first H1 section to parse as title&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;mTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;# (?P&amp;lt;title&amp;gt;.*)\r?\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mTitle&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mTitle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupdict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;title&amp;#34;&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="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="nb"&gt;print&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;#34;WARN: did not find title for file: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some interesting tidbits: if a Markdown file has a &lt;code&gt;.&lt;/code&gt; at the start I assume it
       +is a draft post, and ignore it from my scraper. I added a bunch of &lt;code&gt;WARN&lt;/code&gt;
       +prints to make sure that the me in the future doesn't do anything dumb. Also,
       +sorting in reverse since reverse chronological order is the one most people
       +expect in blogs (i.e.: more recent blog posts at top).&lt;/p&gt;
       +&lt;p&gt;After running the function above, I have a resulting dictionary that I can use
       +to generate either a &lt;code&gt;README.md&lt;/code&gt; file or Markdown:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen_readme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;titles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dayPosts&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dayPosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# This creates a relative link to the Markdown file, .e.g.:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# ./02-using-github-as-a-bad-blog-platform.md&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# This formats the title, e.g.:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# - [Using GitHub as a (bad) blog platform](./2024-07-26/02-using-github-as-a-bad-blog-platform.md) - 2024-07-26&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&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;#34;- [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) - %Y-%m-%d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# This appends to the list to generate the content later&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;titles&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# README_TEMPLATE is a string with the static part of the README&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;README_TEMPLATE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;titles&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen_rss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Got most of the specification from here:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# https://www.w3schools.com/XML/xml_rss.asp&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;rss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rss&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Here are the RSS metadata for the blog itself&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;channel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kokada&amp;#39;s blog&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;link&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/thiagokokada/blog&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dd if=/dev/urandom of=/dev/brain0&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# You create one item for each blog post&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dayPost&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dayPost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;item&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RSS_POST_LINK_PREFIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;guid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;link&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pubDate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%a&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt; %b %Y %H:%M:%S GMT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# Generate the XML and indent&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElementTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;tree&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="s2"&gt;&amp;#34;rss.xml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xml_declaration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To publish a new Post, a basically write a Markdown file, run `./gen_readme.py&lt;/p&gt;
       +&lt;blockquote&gt;
       +&lt;p&gt;README.md` at the root of the repository, and see the magic happen.&lt;/p&gt;
       +&lt;/blockquote&gt;
       +&lt;p&gt;It works much better than I initially anticipated. The &lt;code&gt;README.md&lt;/code&gt; is properly
       +populated with the titles and links. The RSS is kind of empty since it has no
       +description, but it seems to work fine (at least in
       +&lt;a href="https://www.inoreader.com/"&gt;Inoreader&lt;/a&gt;, my RSS reader of choice). I can
       +probably fill the post description with more information if I really want, but
       +it is enough for now (update: it is working now, you just need to render the
       +Markdown as HTML and escape the tags;
       +&lt;a href="https://github.com/thiagokokada/blog/blob/9506051cc3d49f203304174b335ff62de9d17a05/gen_blog.py"&gt;permalink&lt;/a&gt;
       +for the updated script). Not sure who is that interested in my writing that
       +will want to use this RSS feed instead the one available in
       +&lt;a href="https://kokada.capivaras.dev/rss/"&gt;capivaras.dev&lt;/a&gt; anyway.&lt;/p&gt;
       +&lt;p&gt;Also, while I am using GitHub here, the same idea would work in GitLab, Gitea,
       +sr.ht or whatever. As long as your source hub supports Markdown files it should
       +work.&lt;/p&gt;
       +&lt;p&gt;So that is it. I am not saying this is a good idea for your primary blog
       +platform or whatever, and I still prefer to publish to a platform that doesn't
       +track users or have tons of JavaScript or whatever. But if you want a backup of
       +your posts and you are already writing Markdown anyway, well, there are worse
       +ways to do it I think.&lt;/p&gt;
       +&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; I rewrote the script again using Go
       +(&lt;a href="https://github.com/thiagokokada/blog/blob/3ece3853e7dd32977e7ec5238d2955e530ca4beb/blog.go"&gt;permalink&lt;/a&gt;).
       +The reason for it is because when I started rendering Markdown (for
       +descriptions) the Python version got quite slow (not the fault of Python
       +itself, mostly because of the usage of &lt;code&gt;nix-shell&lt;/code&gt; to manage dependencies;
       +something that Go doesn't need). Took about half an hour, showing how easy it
       +is to do the same.&lt;/p&gt;</description><pubDate>Fri, 26 Jul 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/using-github-as-a-bad-blog-platform/</guid></item><item><title>Writing NixOS tests for fun and profit</title><link>https://kokada.dev/blog/writing-nixos-tests-for-fun-and-profit/</link><description>&lt;p&gt;I recently started a &lt;a href="https://github.com/thiagokokada/hyprland-go"&gt;new side
       +project&lt;/a&gt; writing an IPC library in
       +Go for &lt;a href="https://hyprland.org/"&gt;Hyprland&lt;/a&gt;, a Window Manager for Wayland.&lt;/p&gt;
       +&lt;p&gt;Once I got past the Work-in-Progress phase, I realise I had an issue: I wrote
       +some tests, but I was running then inside my system running Hyprland. And the
       +tests themselves were annoying: since they send commands to the current running
       +Hyprland instance, I was having programs being opened and settings being
       +changed, because this was the only way to have a reasonable good confidence
       +that what I was doing was correct. So I need to do like any good developer and
       +implement a CI, but how?&lt;/p&gt;
       +&lt;p&gt;One approach would be to create something like a mock client and test against
       +my mock. Since this mock wouldn't need a running Hyprland instance the tests
       +could run everywhere (even in non-Linux systems!), but they wouldn't be much
       +useful. Mocks are great for testing business logic, but not really for making
       +sure everything is working correctly.&lt;/p&gt;
       +&lt;p&gt;I need something more akin to an integration test, but this is tricky. It is
       +not like I am doing integration with e.g.: PostgreSQL that has thousands of
       +libraries available to make integration tests easier, I am doing integration
       +with a Window Manager that is a moving target with multiple breaking changes in
       +each release. And this is where NixOS tests enter, a way to run tests inside
       +Virtual Machines configured in Nix.&lt;/p&gt;
       +&lt;p&gt;I am a long time NixOS user and commiter, but I never wrote a NixOS test
       +outside of &lt;a href="https://github.com/NixOS/nixpkgs"&gt;nixpkgs&lt;/a&gt; itself. However I knew
       +it was possible, and after doing a quick reading of the &lt;a href="https://wiki.nixos.org/wiki/NixOS_VM_tests"&gt;Wiki
       +entry&lt;/a&gt; about it, I was ready to
       +start.&lt;/p&gt;
       +&lt;p&gt;The first part is to call &lt;code&gt;pkgs.testers.runNixOSTest&lt;/code&gt; and configure the machine
       +as any other NixOS system, e.g.:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;testers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runNixOSTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hyprland-go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# bootloader related configuration&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemd-boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;efi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canTouchEfiVariables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# enable hyprland&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hyprland&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# create a user called alice&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;isNormalUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# add some extra packages that we need during tests&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;kitty&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# auto login as alice&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autologinUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# configure VM, increase memory and CPU and enable OpenGL via LLVMPipe&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;virtualisation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qemu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-smp 2&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-m 4G&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-vga none&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="s2"&gt;&amp;#34;-device virtio-gpu-pci&amp;#34;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;];&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# Start hyprland at login&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loginShellInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hyprland&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;testScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;start_all()&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A few details that I want to bring to attention. The first one is how easy it
       +is to setup things like a normal user account, add some extra packages we need
       +for testing, add Hyprland itself and configure auto-login. I have no idea how
       +painful it would be to automatise all those steps in e.g.: Ansible, but here we
       +are in a few lines of Nix code. This is, of course, thanks to all the
       +contributors to nixpkgs that implement something that help their own use case,
       +but once combined make it greater than the sum of the parts.&lt;/p&gt;
       +&lt;p&gt;Second is something that I took a while to figure out: how to enable GPU
       +acceleration inside the VM. You see, Hyprland, different from other Window
       +Managers, requires OpenGL support. This is basically why the flag &lt;code&gt;-device virtio-gpu-pci&lt;/code&gt; is in &lt;code&gt;virtualisation.qemu.options&lt;/code&gt;, this enables OpenGL
       +rendering via LLVMPipe, that while being slow since it is rendered in CPU, is
       +sufficient for this case.&lt;/p&gt;
       +&lt;p&gt;Putting the above code inside a
       +&lt;a href="https://wiki.nixos.org/wiki/Flakes"&gt;&lt;code&gt;flake.nix&lt;/code&gt;&lt;/a&gt; for reproducibility, I had
       +something similar to:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hyprland&amp;#39;s IPC bindings for Go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:NixOS/nixpkgs/nixos-unstable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x86_64-linux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;in&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="n"&gt;testVm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;testers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runNixOSTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="c1"&gt;# the code above&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I can now run &lt;code&gt;nix build .#checks.x86_64-linux.testVm -L&lt;/code&gt; to build and run the
       +VM. However it is not really useful right now, since we didn't add any useful
       +code in &lt;code&gt;testScript&lt;/code&gt;, the core of the NixOS test framework. We can also run
       +&lt;code&gt;nix build .#checks.x86_64-linux.testVm.driverInteractive&lt;/code&gt; and
       +&lt;code&gt;./result/bin/nixos-test-driver&lt;/code&gt;: this will start a Python console where we can
       +manually play with the VM (try typing &lt;code&gt;start_all()&lt;/code&gt; for example).&lt;/p&gt;
       +&lt;p&gt;The &lt;code&gt;testScript&lt;/code&gt; is a sequence of Python statements that perform various
       +actions, such as starting VMs, executing commands in the VMs, and so on. More
       +about it in the official
       +&lt;a href="https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests"&gt;documentation&lt;/a&gt;.
       +For our case we can start with something like this:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;testScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* python */&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      start_all()
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      machine.wait_for_unit(&amp;#34;multi-user.target&amp;#34;)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      machine.wait_for_file(&amp;#34;/home/alice/test-finished&amp;#34;)
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first statement, &lt;code&gt;start_all()&lt;/code&gt;, starts all VMs, in this case we have only
       +one, called &lt;code&gt;machine&lt;/code&gt;. We send two further commands to &lt;code&gt;machine&lt;/code&gt;:
       +&lt;code&gt;wait_for_unit(&amp;quot;multi-user.target&amp;quot;)&lt;/code&gt; and
       +&lt;code&gt;wait_for_file(&amp;quot;/home/alice/test-finished&amp;quot;)&lt;/code&gt;.&lt;/p&gt;
       +&lt;p&gt;The first command waits until systemd's &lt;code&gt;multi-user.target&lt;/code&gt; is ready, a good
       +way to ensure that the system is ready for further commands. The second one we
       +wait for a file called &lt;code&gt;test-finished&lt;/code&gt; to appear in Alice's &lt;code&gt;$HOME&lt;/code&gt; (basically,
       +a canary), but how can we generate this file?&lt;/p&gt;
       +&lt;p&gt;Remember that we added &lt;code&gt;programs.bash.loginShellInit = &amp;quot;Hyprland&amp;quot;&lt;/code&gt;, that
       +automatically starts Hyprland when Alice logs in. We need to modify that
       +command to run the Go tests from our library. The good thing is that Hyprland
       +configuration file supports a
       +&lt;a href="https://wiki.hyprland.org/Configuring/Keywords/#executing"&gt;&lt;code&gt;exec-once&lt;/code&gt;&lt;/a&gt;
       +command that runs a command during Hyprland launch. We can abuse this to launch
       +a terminal emulator and run our tests:&lt;/p&gt;
       +&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loginShellInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;let&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;testScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeShellScript&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hyprland-go-test&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        set -euo pipefail
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        trap &amp;#39;echo $? &amp;gt; $HOME/test-finished&amp;#39; EXIT # creates the canary when the script finishes
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        cd &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="sr"&gt;./.&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; # go to the library directory
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        go test -v ./... &amp;gt; $HOME/test.log 2&amp;gt;&amp;amp;1 # run Go tests
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;hyprlandConf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeText&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hyprland.conf&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;        exec-once = kitty sh -c &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;testScript&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;      Hyprland --config &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;hyprlandConf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       +&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we are basically creating a custom Hyprland config that starts a
       +&lt;a href="https://sw.kovidgoyal.net/kitty/"&gt;Kitty&lt;/a&gt; terminal emulator, that then launches
       +a shell script that runs the test. Since we have no way to get the results of
       +the test, we pipe the output to a file that we can collect later (e.g.:
       +&lt;code&gt;machine.succeded(&amp;quot;cat /home/alice/test.log&amp;quot;)&lt;/code&gt;). And once the script exit, we
       +create the canary file &lt;code&gt;$HOME/test-finished&lt;/code&gt;, that allows the &lt;code&gt;testScript&lt;/code&gt;
       +knows that the test finished and it can destroy the VM safely.&lt;/p&gt;
       +&lt;p&gt;If you want to take a look at the final result, it is
       +&lt;a href="https://github.com/thiagokokada/hyprland-go/blob/v0.0.1/flake.nix"&gt;here&lt;/a&gt;. This
       +tests run in any Linux machine that supports KVM, and also works in &lt;a href="https://github.com/thiagokokada/hyprland-go/actions/workflows/nix.yaml"&gt;GitHub
       +Actions&lt;/a&gt;
       +thanks to the the
       +&lt;a href="https://github.com/DeterminateSystems/nix-installer-action"&gt;nix-installer-action&lt;/a&gt;.&lt;/p&gt;
       +&lt;p&gt;And now I have a proper CI pipeline in a way that I never imagined would be
       +possible, especially considering how simple it was.&lt;/p&gt;</description><pubDate>Fri, 26 Jul 2024 00:00:00 +0000</pubDate><guid>https://kokada.dev/blog/writing-nixos-tests-for-fun-and-profit/</guid></item></channel></rss>
       +\ No newline at end of file