kokada.dev.atom.xml - 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
       ---
       kokada.dev.atom.xml (494691B)
       ---
            1 <?xml version="1.0" encoding="utf-8"?>
            2 <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
            3 post&lt;/a&gt;. It is mostly
            4 because I ran out of things to write, but I expected this. This is probably
            5 more likely how I am actually going to post from now. At least, it shows that
            6 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
            7 time&lt;/a&gt;, that is
            8 easy to go back when I wanted is working fine, but I digress.&lt;/p&gt;
            9 &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
           10 page&lt;/a&gt; for the first time in years. I
           11 hate &lt;a href="https://en.wikipedia.org/wiki/Troff"&gt;troff&lt;/a&gt;, the typesetting system used
           12 for man pages (similar to &lt;a href="https://en.wikipedia.org/wiki/LaTeX"&gt;LaTeX&lt;/a&gt; for
           13 documents). It is one of the weirdest languages that I ever saw, and even the
           14 example in Wikipedia shows that:&lt;/p&gt;
           15 &lt;pre&gt;&lt;code class="language-troff"&gt;.ND &amp;quot;January 10, 1993&amp;quot;
           16 .AU &amp;quot;Ms. Jane Smith&amp;quot;
           17 .AT &amp;quot;Upcoming appointment&amp;quot;
           18 .MT 5
           19 .DS
           20 Reference #A12345
           21 .sp 4
           22 Mr. Samuel Jones
           23 Field director, Bureau of Inspections
           24 1010 Government Plaza
           25 Capitoltown, ST
           26 .sp 3
           27 Dear Mr. Jones,
           28 .sp 2
           29 .P
           30 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
           31 .B now ,
           32 in
           33 .I this
           34 year of
           35 .I this
           36 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.
           37 .P
           38 Thank you for your consideration of our position.
           39 .FC Sincerely
           40 .SG
           41 &lt;/code&gt;&lt;/pre&gt;
           42 &lt;p&gt;Keep in mind that the break lines are necessary every time you introduce a
           43 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
           44 as illegible as hell, and it is worse that the format lacks good tooling (or at
           45 least I didn't find any good ones).&lt;/p&gt;
           46 &lt;p&gt;Most people when they need to write a man page nowadays ends up using some
           47 other format that generates a man page. For example, in the past I used
           48 &lt;a href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt; to convert Markdown to a man page, but even if
           49 Pandoc is a great project the result is sub-optimal at best: Markdowns are, at
           50 the end, designed for generating HTML (and a subset of it), and not man pages,
           51 so you basically ends up fighting the format for it to do what you want.
           52 Also, Pandoc is a big project, with a ~200MB binary (at least it is the default
           53 Pandoc binary in Nix).&lt;/p&gt;
           54 &lt;p&gt;For this specific project I needed something small. I am trying to replace one
           55 of the most essential pieces inside NixOS, &lt;code&gt;nixos-rebuild&lt;/code&gt;, written in Bash,
           56 with a &lt;a href="https://discourse.nixos.org/t/nixos-rebuild-ng-a-nixos-rebuild-rewrite/55606/"&gt;full rewritten in
           57 Python&lt;/a&gt;
           58 (sorry Rust zealots!), called &lt;code&gt;nixos-rebuild-ng&lt;/code&gt;.&lt;/p&gt;
           59 &lt;p&gt;Since this project will eventually (if successful) be in the critical path for
           60 NixOS, I want to reduce the number of dependencies as much as possible, so
           61 something as big as Pandoc is out. I could use
           62 &lt;a href="https://asciidoc.org/"&gt;AsciiDoc&lt;/a&gt;, but it is a big complicated Python project
           63 (this may seem ironic, but &lt;code&gt;nixos-rebuild-ng&lt;/code&gt; has only one runtime dependency,
           64 that is optional). And I also hated the last time I tried to use it to generate
           65 man pages: it more flexible than Markdown, but still far from optimal.&lt;/p&gt;
           66 &lt;p&gt;Thanks to Drew DeVault (creator of &lt;a href="https://swaywm.org/"&gt;SwayWM&lt;/a&gt;) that seems it
           67 had the same issues in the past and created
           68 &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
           69 page generator using a DSL inspired in Markdown, but specific to generate man
           70 pages. The binary is written in C (and advantage in this case since it means it
           71 is easier to bootstrap), is small (~1 Kloc) and has no dependencies, so it
           72 fits the requirement.&lt;/p&gt;
           73 &lt;p&gt;While the language suffers from being a niche project for a niche segment, the
           74 &lt;a href="https://man.archlinux.org/man/scdoc.5.en"&gt;man page&lt;/a&gt; for it is actually really
           75 nice. It is terse though and lacks examples, and this is what this blog post
           76 will try to accomplish.&lt;/p&gt;
           77 &lt;p&gt;To start, let's have a quick summary of the syntax, written in &lt;code&gt;scdoc&lt;/code&gt; as
           78 comments:&lt;/p&gt;
           79 &lt;pre&gt;&lt;code class="language-scdoc"&gt;; quick summary:
           80 ; # new section
           81 ; comments starts with ;
           82 ; - this is a list
           83 ;         - sub-list
           84 ; - *bold*: _underline_, force a line break++
           85 ; - [tables], \[ can be used to force an actual [
           86 ; . numbered list
           87 ; please configure your editor to use hard tabs
           88 ; see `man 5 scdoc` for more information about syntax
           89 ; or https://man.archlinux.org/man/scdoc.5.en
           90 &lt;/code&gt;&lt;/pre&gt;
           91 &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
           92 I wrote, so it is easy for someone that never saw the format to start
           93 collaborating.&lt;/p&gt;
           94 &lt;p&gt;And here an example of a (summarised) man page in &lt;code&gt;.scd&lt;/code&gt; format:&lt;/p&gt;
           95 &lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nixos-rebuild-ng(8)
           96 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
           97 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# NAME
           98 &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;
           99 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nixos-rebuild - reconfigure a NixOS machine
          100 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          101 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# SYNOPSIS
          102 &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;
          103 &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]++
          104 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                \[{switch,boot}]
          105 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          106 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# DESCRIPTION
          107 &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;
          108 &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.
          109 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It must be one of the following:
          110 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          111 &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;
          112 &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.
          113 &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
          114 &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
          115 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        into the new configuration. Previous configurations activated with
          116 &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
          117 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        menu.
          118 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          119 &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;
          120 &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
          121 &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
          122 &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.
          123 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          124 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# OPTIONS
          125 &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;
          126 &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;
          127 &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
          128 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        system.
          129 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          130 &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
          131 &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
          132 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        also be updated.
          133 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          134 &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.
          135 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          136 &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.
          137 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          138 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# ENVIRONMENT
          139 &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;
          140 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NIXOS_CONFIG
          141 &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
          142 &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;.
          143 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          144 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# FILES
          145 &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;
          146 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/etc/nixos/flake.nix
          147 &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
          148 &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
          149 &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
          150 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        flake.
          151 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          152 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# AUTHORS
          153 &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;
          154 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Nixpkgs/NixOS contributors
          155 &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;
          156 &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
          157 file"&gt;&lt;/a&gt;&lt;/p&gt;
          158 &lt;p&gt;One of nice things that I found is how looking at the plain text looks kind
          159 like the man page result already. And if you know Markdown, you can basically
          160 understand most things that is happening. There are a few differences, like
          161 &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
          162 end of the world.&lt;/p&gt;
          163 &lt;p&gt;Now, the format has its quirks. The first line being the name of the program
          164 and section in parenthesis is required, but this makes sense, since you need
          165 this information for the corners. But for one, it requires the usage of hard
          166 tabs to create indentation, and the error messages are awful, in a situation
          167 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
          168 means that the traditional &lt;code&gt;app [command]&lt;/code&gt; needs in many cases to be escaped as
          169 &lt;code&gt;app \[command]&lt;/code&gt;. I found this a strange choice since this is supposed to be a
          170 format that is only used for man pages, and using &lt;code&gt;[command]&lt;/code&gt; to indicate an
          171 optional is common, but at least it is easy to escape.&lt;/p&gt;
          172 &lt;p&gt;In the end, I think all that matters is the result. And for the first time for
          173 all those years trying to write a man page, I am satisfied with the result. The
          174 man page looks exactly as I wanted once rendered, and the &lt;code&gt;.scd&lt;/code&gt; file looks
          175 reasonable good that it can work as a documentation for someone that for one
          176 reason or another can't use the man page (can't say the same for the troff
          177 version). Also, it is really easy for someone to just go there and update the
          178 man page, even without experience in the format (except for maybe the
          179 requirement of tabs). So all in all, I really liked the format, and will use it
          180 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
          181 wireless earbuds instead of wired earphones is the latency: it is bad, getting
          182 up to seconds(!) depending on your particular combination of OS/earbuds/device.&lt;/p&gt;
          183 &lt;p&gt;There is a solution though: Bluetooth LE Audio, that is supposed to fix
          184 multiple issues with the original design for Bluetooth Classic Audio, including
          185 a much lower latency, improved efficiency (e.g.: less battery power) and even
          186 multiple streams of audio. LE Audio also includes a new default codec for
          187 improved audio quality, &lt;a href="https://en.wikipedia.org/wiki/LC3_(codec)"&gt;LC3&lt;/a&gt;, that
          188 replaces the venerable &lt;a href="https://en.wikipedia.org/wiki/SBC_(codec)"&gt;SBC&lt;/a&gt; codec
          189 for audio.&lt;/p&gt;
          190 &lt;p&gt;However, the standard is a mess right now: a few wireless headphones already
          191 support it, but they're generally disabled by default and it is pretty messy to
          192 enable. And even after enabling it, getting it to work can be a pain.&lt;/p&gt;
          193 &lt;p&gt;I have pretty much the best setup to use LE Audio right now: a recently
          194 released Pixel 9 Pro with Sony's
          195 &lt;a href="https://www.sony.ie/headphones/products/wf-1000xm5"&gt;WF-1000XM5&lt;/a&gt; earbuds, and
          196 after lots of tries I got it to work. You can see below the versions of
          197 everything I am using:&lt;/p&gt;
          198 &lt;ul&gt;
          199 &lt;li&gt;Android: 14&lt;/li&gt;
          200 &lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.sony.songpal.mdr"&gt;Sound
          201 Connect&lt;/a&gt;:
          202 11.0.1&lt;/li&gt;
          203 &lt;li&gt;WM-1000XM5: 4.0.2&lt;/li&gt;
          204 &lt;/ul&gt;
          205 &lt;p&gt;The first thing you need to do is enable in &amp;quot;Sound Connect&amp;quot; app &amp;quot;LE Audio
          206 Priority&amp;quot; in &amp;quot;Device Settings -&amp;gt; System&amp;quot;:&lt;/p&gt;
          207 &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
          208 Connect"&gt;&lt;/a&gt;&lt;/p&gt;
          209 &lt;p&gt;After this, you will need to pair your headset with the device again. You can
          210 do this as same as always: press and hold the button in case for a few seconds
          211 until a blue light starts to blink. However, this is where things starts to get
          212 janky: I couldn't get the headset to pair with Android again.&lt;/p&gt;
          213 &lt;p&gt;A few of the things that I needed to do (in no specific order):&lt;/p&gt;
          214 &lt;ul&gt;
          215 &lt;li&gt;Remove the previous paired headset&lt;/li&gt;
          216 &lt;li&gt;Restart the Android&lt;/li&gt;
          217 &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;
          218 &amp;quot;Storage and Cache&amp;quot; -&amp;gt; &amp;quot;Clear storage&amp;quot;)&lt;/li&gt;
          219 &lt;/ul&gt;
          220 &lt;p&gt;If you can get the headset to connect, go to the &amp;quot;Bluetooth&amp;quot; settings in
          221 Android, click in the gear icon for the headset and enable &amp;quot;LE Audio&amp;quot; option:&lt;/p&gt;
          222 &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
          223 Settings"&gt;&lt;/a&gt;&lt;/p&gt;
          224 &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
          225 settings&lt;/a&gt;
          226 and try again from the start (that means pairing your device with &amp;quot;Sound
          227 Connect&amp;quot; again, and you may want to try to clear the storage before doing so).&lt;/p&gt;
          228 &lt;p&gt;Yes, the process is extremely janky, but I think this is why both &amp;quot;Sound
          229 Connect&amp;quot; and Android marks this feature as beta/experimental. And I still need
          230 to test the latency, but from my initial testing there are some glitches when
          231 the audio is only used for a short period of time (e.g.: Duolingo only enables
          232 the audio when the character is speaking). So I only recommend this if you want
          233 to test how LE Audio will behave, since it is clear that this needs more
          234 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;
          235 &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
          236 &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
          237 &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
          238 &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
          239 is this stored? Well, I had to look at the Nix source code to find the answer,
          240 since I can't find this anywhere in the web or in ChatGPT (but I am sure that
          241 now that I posted this it will eventually be searchable), and the answer can be
          242 found
          243 &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;
          244 &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;
          245 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          246 &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;
          247 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          248 &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
          249 &lt;a href="https://github.com/NixOS/nix/blob/c116030605bf7fecd232d0ff3b6fe066f23e4620/src/libutil/users.cc#L52-L65"&gt;here&lt;/a&gt;:&lt;/p&gt;
          250 &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;
          251 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          252 &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;
          253 &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;
          254 &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;
          255 &lt;/span&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;
          256 &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;
          257 &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;
          258 &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;
          259 &lt;/span&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;
          260 &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;
          261 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
          262 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
          263 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          264 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we solved the mystery:&lt;/p&gt;
          265 &lt;ul&gt;
          266 &lt;li&gt;If &lt;code&gt;NIX_DATA_HOME&lt;/code&gt; is set, the file will be in
          267 &lt;code&gt;$NIX_DATA_HOME/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
          268 &lt;li&gt;If &lt;code&gt;XDG_DATA_HOME&lt;/code&gt; is set, the file will be in
          269 &lt;code&gt;$XDG_DATA_HOME/nix/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
          270 &lt;li&gt;Otherwise Nix will fallback to &lt;code&gt;$HOME/.local/share/nix/trusted-settings.json&lt;/code&gt;&lt;/li&gt;
          271 &lt;/ul&gt;
          272 &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
          273 &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
          274 attribute (like &lt;code&gt;extra-substituters&lt;/code&gt;) you will get this prompt, unless you set
          275 &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;
          276 in your Nix configuration (please &lt;strong&gt;do not do this&lt;/strong&gt;, it is dangerous because
          277 it may allow running possible unsafe options without asking you first).&lt;/p&gt;
          278 &lt;p&gt;You can inspect the JSON file or delete it and Nix will prompt you again the
          279 next time you run a &lt;code&gt;nix&lt;/code&gt; command. And yes, saving this preference is
          280 controversial considering this is Nix, but having the power to set &lt;code&gt;nixConfig&lt;/code&gt;
          281 attributes is really powerful, and with great powers comes great
          282 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
          283 everywhere you go. However, I like to talk about tools, and AIs, or more
          284 specifically, &lt;a href="https://en.wikipedia.org/wiki/Large_language_model"&gt;Large Language
          285 Models&lt;/a&gt; (LLM), can be
          286 really useful tools. Maybe someday we will have our AI overlords dominating
          287 everything (including my job), but right now I don't feel particularly
          288 threatened given my experience with AIs.&lt;/p&gt;
          289 &lt;p&gt;Given that &lt;a href="https://mashable.com/article/google-search-low-quality-research"&gt;Google Search is getting
          290 worse&lt;/a&gt;, I am
          291 slowly relying in LLMs more and more. This is the focus of this post, basically
          292 &lt;a href="https://www.rottentomatoes.com/m/the_good_the_bad_and_the_ugly"&gt;the good, the bad and the
          293 ugly&lt;/a&gt; (by the
          294 way, never saw this film) of LLMs, especially focused for solving coding
          295 problems. I will take a look at the my chat history in
          296 &lt;a href="https://chatgpt.com/"&gt;ChatGPT&lt;/a&gt; and see where it was a good tool, where it
          297 shined, and where it just completely wasted my time.&lt;/p&gt;
          298 &lt;p&gt;A disclaimer, I never bothered to buy a ChatGPT subscription because I don't
          299 feel that the tool is useful enough (at least yet), so most of the examples
          300 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
          301 mini&lt;/a&gt;,
          302 since those where the models offered by free in ChatGPT.&lt;/p&gt;
          303 &lt;p&gt;An addendum, I have some experience with &lt;a href="https://github.com/features/copilot"&gt;GitHub
          304 Copilot&lt;/a&gt; (another LLM, but this one runs
          305 inside your IDE or favorite text editor) in &lt;code&gt;$PREVIOUS_JOB&lt;/code&gt;. But my experience
          306 with it is even more underwhelming than what I am going to describe in this
          307 post: it was basically really good for generating fixture for tests and between
          308 ok-ish to plain bad for everything else. Yes, that's would fit in a tweet (or
          309 xeet?).&lt;/p&gt;
          310 &lt;h2&gt;The good&lt;/h2&gt;
          311 &lt;p&gt;&lt;a href="https://chatgpt.com/share/c58a1432-2761-4954-acac-cade29942c34"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
          312 &lt;blockquote&gt;
          313 &lt;p&gt;Is there any way that I can pass a context for a net.conn to cancel its
          314 Read()?&lt;/p&gt;
          315 &lt;/blockquote&gt;
          316 &lt;p&gt;This is one of the good cases where ChatGPT felt to me that I was talking with
          317 an expert in Go. To start, I didn't even mention in my initial prompt that I
          318 was talking about Go, but it correctly inferred so. Also, the solution overview
          319 was great:&lt;/p&gt;
          320 &lt;blockquote&gt;
          321 &lt;ol&gt;
          322 &lt;li&gt;&lt;strong&gt;Use a separate goroutine&lt;/strong&gt; to handle the context cancellation.&lt;/li&gt;
          323 &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
          324 the &lt;code&gt;Read()&lt;/code&gt; when the context is canceled.&lt;/li&gt;
          325 &lt;/ol&gt;
          326 &lt;/blockquote&gt;
          327 &lt;p&gt;Before asking ChatGPT about this issue I had already coded my own naive
          328 solution that was something similar to this:&lt;/p&gt;
          329 &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;
          330 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// ...
          331 &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;
          332 &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;
          333 &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;
          334 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          335 &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;
          336 &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;
          337 &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;
          338 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
          339 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          340 &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;
          341 &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;
          342 &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;
          343 &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;
          344 &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;
          345 &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;
          346 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
          347 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// ...
          348 &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;
          349 &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;
          350 &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;
          351 &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;
          352 &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;
          353 &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;
          354 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          355 &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
          356 &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;
          357 &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;
          358 &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;
          359 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
          360 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          361 &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;
          362 &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
          363 &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()
          364 &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;
          365 &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
          366 &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;
          367 &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
          368 &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;
          369 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
          370 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          371 &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;
          372 &lt;ol&gt;
          373 &lt;li&gt;It doesn't close the connection socket allowing it to be reused&lt;/li&gt;
          374 &lt;li&gt;It waits until the goroutine is finished to avoid leaks&lt;/li&gt;
          375 &lt;li&gt;It closes the channel after it finishes&lt;/li&gt;
          376 &lt;/ol&gt;
          377 &lt;p&gt;Now, I don't claim to be a Go expert (far from it), but this was one of those
          378 cases where I felt that I was being guided by someone that is coding in Go for
          379 years.&lt;/p&gt;
          380 &lt;p&gt;But it didn't end there, I asked:&lt;/p&gt;
          381 &lt;blockquote&gt;
          382 &lt;p&gt;If I want to reuse the connection, do I need to reset the SetReadDeadline?&lt;/p&gt;
          383 &lt;/blockquote&gt;
          384 &lt;p&gt;Since in this particular case I wanted the underlying connection to be reused,
          385 and ChatGPT correctly amended the code:&lt;/p&gt;
          386 &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;
          387 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;// Same as before
          388 &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;
          389 &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
          390 &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()
          391 &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;
          392 &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
          393 &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;
          394 &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
          395 &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;
          396 &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;
          397 &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
          398 &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;
          399 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
          400 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          401 &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;
          402 &lt;blockquote&gt;
          403 &lt;p&gt;When you set a read deadline with &lt;code&gt;conn.SetReadDeadline(time.Now())&lt;/code&gt; to unblock
          404 the &lt;code&gt;Read()&lt;/code&gt; call, this deadline remains in effect for future &lt;code&gt;Read()&lt;/code&gt;
          405 operations. This means that if you attempt another read operation after this,
          406 it could immediately fail with a timeout error if the deadline is still in
          407 effect.
          408 To reuse the connection, you'll need to remove the deadline or set a new one.
          409 You can do this by calling &lt;code&gt;conn.SetReadDeadline(time.Time{})&lt;/code&gt;, which clears
          410 any existing deadline.&lt;/p&gt;
          411 &lt;/blockquote&gt;
          412 &lt;p&gt;I had one last question that I kind already knew the answer:&lt;/p&gt;
          413 &lt;blockquote&gt;
          414 &lt;p&gt;Do we need to defer the clearing?&lt;/p&gt;
          415 &lt;/blockquote&gt;
          416 &lt;p&gt;And ChatGPT correctly returned:&lt;/p&gt;
          417 &lt;blockquote&gt;
          418 &lt;p&gt;Whether you need to defer the clearing of the read deadline depends on how
          419 your code is structured. If you want to ensure that the read deadline is
          420 always cleared after a context cancellation, even if the code panics or exits
          421 early, then deferring the &lt;code&gt;conn.SetReadDeadline(time.Time{})&lt;/code&gt; call is a good
          422 idea.&lt;/p&gt;
          423 &lt;/blockquote&gt;
          424 &lt;p&gt;This whole interaction was one of the best ones I ever had in ChatGPT, one that
          425 it gave me an actual functional code with good explanation on what was
          426 happening. But still, it didn't talked at all about the gotcha of setting the
          427 read deadline and reusing the underlying connection, needing another prompt to
          428 give me this information.&lt;/p&gt;
          429 &lt;p&gt;Also, there is one issue in ChatGPT solution though: we are discarding the
          430 values of the buffer once the context is done by returning &lt;code&gt;n = 0&lt;/code&gt; instead of
          431 the number of bytes read. In this particular case this is what I wanted, but
          432 this is a possible issue that ChatGPT didn't talk.&lt;/p&gt;
          433 &lt;p&gt;For comparison,
          434 &lt;a href="https://stackoverflow.com/questions/61637359/how-to-use-context-context-with-tcp-connection-read"&gt;this&lt;/a&gt;
          435 is the best that I got searching in both DuckDuckGo and Google, that is where I
          436 kind based my first solution on. I still not sure where ChatGPT could give me
          437 such a good answer for this question, because I just couldn't find anything
          438 remotely similar during my searches.&lt;/p&gt;
          439 &lt;h2&gt;The amazing&lt;/h2&gt;
          440 &lt;p&gt;&lt;a href="https://chatgpt.com/share/0c9a148f-4cfd-4b4e-aa84-7486e185125b"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
          441 &lt;blockquote&gt;
          442 &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
          443 me to rewrite &lt;code&gt;[link](../2024-07-30/post.md)&lt;/code&gt; to
          444 &lt;code&gt;[link](https://kokada.capivaras.dev/slug)&lt;/code&gt;&lt;/p&gt;
          445 &lt;/blockquote&gt;
          446 &lt;p&gt;Here is the generated code:&lt;/p&gt;
          447 &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;
          448 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          449 &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;
          450 &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;
          451 &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;
          452 &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;
          453 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          454 &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;
          455 &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;
          456 &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;
          457 &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;
          458 &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;
          459 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          460 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          461 &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
          462 &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;
          463 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          464 &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
          465 &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;
          466 &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;
          467 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          468 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          469 &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
          470 &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;
          471 &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;
          472 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          473 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          474 &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
          475 &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;
          476 &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;
          477 &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;
          478 &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;
          479 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
          480 &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;
          481 &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;
          482 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
          483 &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;
          484 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;})&lt;/span&gt;
          485 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          486 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          487 &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
          488 &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;
          489 &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;
          490 &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;
          491 &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
          492 &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;
          493 &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;
          494 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
          495 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          496 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          497 &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
          498 &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;
          499 &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
          500 &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;
          501 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          502 &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;
          503 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          504 &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;
          505 &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;
          506 &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;
          507 &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;
          508 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          509 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          510 &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;
          511 &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;
          512 &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;
          513 &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;
          514 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;),&lt;/span&gt;
          515 &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;
          516 &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;
          517 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;),&lt;/span&gt;
          518 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
          519 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
          520 &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
          521 &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;
          522 &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;,
          523 the Markdown renderer that &lt;a href="https://kokada.dev/blog/making-a-blog-for-the-next-10-years"&gt;powers this
          524 blog&lt;/a&gt;, does not
          525 have good examples or documentation (at least I didn't found neither).&lt;/p&gt;
          526 &lt;p&gt;So I was not really hopeful I would get any answers in ChatGPT, but not only it
          527 put me in the correct path, the code was almost correct. It did had some unused
          528 and missing imports, and there were some compilation errors that were easily
          529 fixed. And when it registered the extension, it also added a
          530 &lt;code&gt;html.WithHardWraps()&lt;/code&gt; renderer for no reason at all.&lt;/p&gt;
          531 &lt;p&gt;But this is one of the experiences in LLM that most impressed me. I had no idea
          532 at all on how to solve this problem, and ChatGPT basically gave me the
          533 solution. A &lt;a href="https://github.com/thiagokokada/blog/blob/3a2ea58a0638d1005617d25eabeae0626933db46/link_rewriter.go"&gt;modified version of the
          534 code&lt;/a&gt;
          535 from this chat is still used to power this blog to this day, that also
          536 demonstrates that the code is not garbage (since now I understand what it does
          537 and I can even extend it).&lt;/p&gt;
          538 &lt;p&gt;I am still not sure how the license situation of this code is though. Does
          539 licenses even make sense in AI generated code?&lt;/p&gt;
          540 &lt;h2&gt;The bad&lt;/h2&gt;
          541 &lt;p&gt;&lt;a href="https://chatgpt.com/share/95fcd777-b03c-4cfe-8d1d-12e53f5a4dfe"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
          542 &lt;blockquote&gt;
          543 &lt;p&gt;I want to configure VM options inside IntelliJ by project&lt;/p&gt;
          544 &lt;/blockquote&gt;
          545 &lt;blockquote&gt;
          546 &lt;p&gt;My project uses sbt, and I want to set this in a way that I can share with my
          547 colleagues&lt;/p&gt;
          548 &lt;/blockquote&gt;
          549 &lt;blockquote&gt;
          550 &lt;p&gt;I am using a build.sbt file, but IntelliJ is not loading it's JVM options and
          551 I need to set this up manually. I want to automate this&lt;/p&gt;
          552 &lt;/blockquote&gt;
          553 &lt;p&gt;This is an example of my most common interaction with ChatGPT: it generally
          554 gives some options that could work, but not in my specific case. So I start to
          555 ask for more information until it eventually starts to
          556 &lt;a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)"&gt;hallucinate&lt;/a&gt;.&lt;/p&gt;
          557 &lt;p&gt;In this particular case, the hallucination was in:&lt;/p&gt;
          558 &lt;blockquote&gt;
          559 &lt;p&gt;The sbt-idea-plugin can generate IntelliJ IDEA project files based on your
          560 SBT configuration, including JVM options. This allows you to ensure that
          561 IntelliJ loads the correct settings from the SBT configuration.&lt;/p&gt;
          562 &lt;p&gt;Steps:&lt;/p&gt;
          563 &lt;ol&gt;
          564 &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;
          565 &lt;/ol&gt;
          566 &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;
          567 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
          568 &lt;li&gt;Run the following command in your terminal to regenerate IntelliJ IDEA
          569 project files:&lt;/li&gt;
          570 &lt;/ol&gt;
          571 &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
          572 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
          573 &lt;p&gt;Yes, there is a
          574 &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
          575 doesn't generate project files based in my SBT configuration. It is not even
          576 from the repository that it is supposed to be pointing too.&lt;/p&gt;
          577 &lt;p&gt;I will just not say that this interaction was a waste of time because some of
          578 the other ideas can be applied, but this was the kind of case where I ended up
          579 just being disappointed because the thing that seemed to be a possible solution
          580 for my problem ended up not existing at all.&lt;/p&gt;
          581 &lt;h2&gt;The ugly&lt;/h2&gt;
          582 &lt;p&gt;There are lots of examples where ChatGPT just plain disappointed me. Here is a
          583 few:&lt;/p&gt;
          584 &lt;blockquote&gt;
          585 &lt;p&gt;What is the language that is a fork of Go that has extra features like map?&lt;/p&gt;
          586 &lt;/blockquote&gt;
          587 &lt;p&gt;&lt;a href="https://chatgpt.com/share/2b854bbd-d376-4049-97fe-b47321c1ef07"&gt;Chat history&lt;/a&gt;.&lt;/p&gt;
          588 &lt;p&gt;This is one of those cases where I saw something once and was trying to
          589 remember. ChatGPT just gave me lots of answers that were not a fork of Go
          590 (&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;,
          591 &lt;a href="https://www.ponylang.io"&gt;Pony&lt;/a&gt;), hallucinated a programming language (Gomini),
          592 and said that &lt;a href="https://ziglang.org/"&gt;Zig&lt;/a&gt; is focused in STEM.&lt;/p&gt;
          593 &lt;p&gt;By the way, I eventually remember what the language was:
          594 &lt;a href="https://goplus.org/"&gt;Go+&lt;/a&gt;. To be fair with ChatGPT, I misremembered that the
          595 Go+ had map (as the &lt;a href="https://en.wikipedia.org/wiki/Map_(higher-order_function)"&gt;high-order
          596 function&lt;/a&gt;, not a
          597 hashmap that both Go and Go+ have). But still, Go+ explicit announces that it
          598 is a language for STEM everywhere in their website, and even them ChatGPT
          599 couldn't answer.&lt;/p&gt;
          600 &lt;blockquote&gt;
          601 &lt;p&gt;In IntelliJ, I need to pass some VM options to the run configuration for my
          602 application to run tests, e.g.:&lt;/p&gt;
          603 &lt;pre&gt;&lt;code&gt;--add-exports=java.base/sun.security.x509=ALL-UNNAMED
          604 --add-opens=java.base/sun.security.ssl=ALL-UNNAMED
          605 -Dconfig.resource=api.test.conf
          606 &lt;/code&gt;&lt;/pre&gt;
          607 &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
          608 depending on the subproject I am running inside Idea. Can I parametrize this
          609 in some way?&lt;/p&gt;
          610 &lt;/blockquote&gt;
          611 &lt;p&gt;&lt;a href="https://chatgpt.com/share/aa9bd051-65de-49d9-8d56-0e28de94722c"&gt;Chat history&lt;/a&gt;&lt;/p&gt;
          612 &lt;p&gt;So quick answer: you can't. IntelliJ macros are not powerful enough for this.
          613 ChatGPT itself said so when I asked about the IntelliJ macros
          614 &lt;a href="https://chatgpt.com/share/7a057bf5-bcec-458b-9432-98e3f551ad28"&gt;directly&lt;/a&gt;.&lt;/p&gt;
          615 &lt;p&gt;But in this particular case, ChatGPT happily said &amp;quot;yes, you can parameterize VM
          616 options in IntelliJ IDEA to make them more flexible depending on the subproject
          617 or other context&amp;quot;. Thanks for wasting my time, I guess.&lt;/p&gt;
          618 &lt;h2&gt;Conclusion&lt;/h2&gt;
          619 &lt;p&gt;I do think that LLMs, in particular ChatGPT since this is the one I have more
          620 experience with, are improving. But still, if there is anything that I
          621 concluded after putting all those experiences in a post is that you still need
          622 someone that knows at least as much about whatever you're asking ChatGPT to
          623 actually validate the answer.&lt;/p&gt;
          624 &lt;p&gt;Not only because LLMs hallucinates a lot, but also because even in the &amp;quot;good&amp;quot;
          625 example the LLM needed extra input to figure out about the possible drawback of
          626 the solution. The only case where I felt that ChatGPT outsmarted me was the
          627 &amp;quot;amazing&amp;quot; case, but this was literally the only example that I could find in
          628 all my chat history with ChatGPT that was like this.&lt;/p&gt;
          629 &lt;p&gt;So yes, at least for now I still don't think AI will be our overlords. Maybe
          630 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
          631 more about it than some of my other projects. For some reason or another I want
          632 to make sure that this blog will continue with me for a long time. This is one
          633 of the reasons why I use &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;GitHub as mirror
          634 blog&lt;/a&gt; and why I
          635 created a &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;bunch of
          636 automation&lt;/a&gt;
          637 to make sure I never forget how to maintain this blog.&lt;/p&gt;
          638 &lt;p&gt;Still, there are a bunch of dependencies that I need to make sure they're
          639 working so I can publish blog posts:&lt;/p&gt;
          640 &lt;ul&gt;
          641 &lt;li&gt;Markdown&lt;/li&gt;
          642 &lt;li&gt;A text editor&lt;/li&gt;
          643 &lt;li&gt;Hosting and blog platform&lt;/li&gt;
          644 &lt;li&gt;Shell script and
          645 &lt;a href="https://kokada.dev/blog/abusing-makefiles-for-fun-and-profit"&gt;Makefile&lt;/a&gt;&lt;/li&gt;
          646 &lt;li&gt;Go toolchain and modules&lt;/li&gt;
          647 &lt;/ul&gt;
          648 &lt;p&gt;Let's start with the most important one: the texts themselves, they're all
          649 written in &lt;a href="https://www.markdownguide.org/"&gt;Markdown&lt;/a&gt;. The nice thing about
          650 Markdown is that it is just plain text files with some special notation that
          651 allow you to format text, but the text itself is still legible even if you have
          652 no support to render Markdowns. So it means as long as I can read a plain text
          653 file, I can still read the posts, no issue there. Closely related issue here is
          654 the text editor, but the nice thing about Markdown is that if tomorrow I decide
          655 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
          656 Markdown files without issue. I also use a standardised Markdown implementation
          657 called &lt;a href="https://commonmark.org/"&gt;CommonMark&lt;/a&gt;, that fixes a bunch of ambiguities
          658 of the original Markdown spec.&lt;/p&gt;
          659 &lt;p&gt;The next point is the host (&lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt;) and the blog
          660 platform (&lt;a href="https://github.com/mataroa-blog/mataroa"&gt;Mataroa&lt;/a&gt;). One of the nice
          661 things about Mataroa is that it avoids platform lock-in by having multiple ways
          662 to &lt;a href="https://mataroa.blog/export/"&gt;export your posts&lt;/a&gt;. I could export everything
          663 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
          664 Pages&lt;/a&gt;.&lt;/p&gt;
          665 &lt;p&gt;This is especially nice consider how small &lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt; is,
          666 so it is good to know that if I eventually have issues I could move to
          667 somewhere else. I also have an e-mail backup every month with all posts made
          668 (another &lt;a href="https://hey.mataroa.blog/blog/monthly-auto-exports-via-email/"&gt;Mataroa
          669 feature&lt;/a&gt;), and
          670 of course I have a &lt;a href="https://github.com/thiagokokada/blog"&gt;Git repo&lt;/a&gt; that also
          671 acts as a &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;mirror of this
          672 blog&lt;/a&gt;. So I would
          673 say the chance of losing access to the content is slim.&lt;/p&gt;
          674 &lt;p&gt;One other issue is the URL of the posts that are indexed in many different
          675 places, like &lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;,
          676 &lt;a href="https://lobste.rs/"&gt;Lobte.rs&lt;/a&gt;, etc. This is why I finally decide to bite the
          677 bullet and purchase a proper domain, and this website should now be available
          678 in &lt;a href="kokada.dev"&gt;kokada.dev&lt;/a&gt;. This means that in my eventual exit from
          679 &lt;a href="capivaras.dev"&gt;capivaras.dev&lt;/a&gt;, I can just point my new blog location to my own
          680 domain if needed (it is not as easy since I also need to preserve the post
          681 URLs, but shouldn't be difficult to fix this if I ever need to do so).&lt;/p&gt;
          682 &lt;p&gt;Now for the tools that I use to publish from the original Markdown files to
          683 everything else. Let's start with shell script(s) and Makefile: I decided that
          684 they're less of an issue if they eventually stop working: they're only used to
          685 make my life easier, but I can still publish files manually if needed. Still, I
          686 tried to rewrite both the
          687 &lt;a href="https://github.com/thiagokokada/blog/commit/a0d421ca90f3da059998295c5e3c6c7a6a3f0688"&gt;shell&lt;/a&gt;
          688 and
          689 &lt;a href="https://github.com/thiagokokada/blog/commit/074580065b21fbdaf930aa51968e69f015d49505"&gt;Makefile&lt;/a&gt;
          690 to avoid GNUisms, so in the eventual case that I decide to e.g.: stop using a
          691 GNU/Linux system like NixOS and use a *BSD system instead, I am covered.&lt;/p&gt;
          692 &lt;p&gt;In other projects I would probably add a &lt;code&gt;shell.nix&lt;/code&gt; file and add the GNU
          693 versions of the tools that I am using, but I am trying to avoid Nix while
          694 building this blog as much as I can, mainly because it is not available
          695 everywhere (e.g.: *BSD systems).&lt;/p&gt;
          696 &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
          697 written in
          698 Go&lt;/a&gt;. Go
          699 is a good language when you want to ensure that things will work for a long
          700 time because of its &lt;a href="https://go.dev/blog/compat"&gt;backwards compatibility
          701 guarantee&lt;/a&gt;. Also I don't expect Google dropping Go
          702 development soon, but even if this happen (&lt;a href="https://killedbygoogle.com/"&gt;&amp;quot;killed by
          703 Google&amp;quot;&lt;/a&gt; is a thing after all), it is very likely
          704 some other group or company would adopt its development quickly, considering
          705 &lt;a href="https://www.tiobe.com/tiobe-index/go/"&gt;how popular the language&lt;/a&gt; is.&lt;/p&gt;
          706 &lt;p&gt;However, the &lt;a href="https://github.com/thiagokokada/blog/blob/main/go.mod"&gt;Go
          707 modules&lt;/a&gt; that I depend
          708 are another story:&lt;/p&gt;
          709 &lt;ul&gt;
          710 &lt;li&gt;&lt;a href="https://github.com/elliotchance/orderedmap/"&gt;elliotchance/orderedmap&lt;/a&gt;: an
          711 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
          712 library&lt;/a&gt;&lt;/li&gt;
          713 &lt;li&gt;&lt;a href="https://github.com/gorilla/feeds"&gt;gorilla/feeds&lt;/a&gt;: a RSS generator library&lt;/li&gt;
          714 &lt;li&gt;&lt;a href="https://github.com/gosimple/slug"&gt;gosimple/slug&lt;/a&gt;: a
          715 &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Slug"&gt;slug&lt;/a&gt; generator
          716 library&lt;/li&gt;
          717 &lt;li&gt;&lt;a href="https://github.com/yuin/goldmark"&gt;yuin/goldmark&lt;/a&gt;: a CommonMark parser and
          718 renderer&lt;/li&gt;
          719 &lt;li&gt;&lt;a href="https://github.com/teekennedy/goldmark-markdown"&gt;teekennedy/goldmark-markdown&lt;/a&gt;:
          720 a renderer for Goldmark to render back to Markdown (since Goldmark itself
          721 doesn't have this capacity)&lt;/li&gt;
          722 &lt;/ul&gt;
          723 &lt;p&gt;In common for all those modules are that they're clearly small projects
          724 maintained mostly by one developer. They're all very good, don't get me wrong,
          725 but they're still an reliability issue in the future. There is no guarantee
          726 those repositories will not be deleted tomorrow, for example.&lt;/p&gt;
          727 &lt;p&gt;Yes, &lt;a href="https://proxy.golang.org/"&gt;Go Proxy&lt;/a&gt; exist, but from what I understood
          728 reading its page is that while it caches modules contents, this is not
          729 guarantee:&lt;/p&gt;
          730 &lt;blockquote&gt;
          731 &lt;p&gt;proxy.golang.org does not save all modules forever. There are a number of
          732 reasons for this, but one reason is if proxy.golang.org is not able to detect
          733 a suitable license. In this case, only a temporarily cached copy of the
          734 module will be made available, and may become unavailable if it is removed
          735 from the original source and becomes outdated. The checksums will still
          736 remain in the checksum database regardless of whether or not they have become
          737 unavailable in the mirror.&lt;/p&gt;
          738 &lt;/blockquote&gt;
          739 &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
          740 code of all modules inside the
          741 &lt;a href="https://github.com/thiagokokada/blog/tree/0b97630d6b30551ffe05b5d8124305b1065f729d/vendor"&gt;vendor&lt;/a&gt;
          742 directory in the repository, avoiding the risks I commented above. This allows
          743 me to ensure that this blog will still be publishable in the future, as long as
          744 I have a working Go toolchain (and Go toolchain makes this
          745 &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;
          746 &lt;p&gt;There are a few other things that can bitrot this blog, for example links going
          747 nowhere. I always try to use
          748 &lt;a href="https://en.wikipedia.org/wiki/Permalink"&gt;permalinks&lt;/a&gt; when it makes sense, but
          749 the only actual way to ensure those links would work in the future would be to
          750 point them to &lt;a href="https://archive.org/"&gt;archive.org&lt;/a&gt; (but even archive.org may not
          751 exist forever). Maybe something to fix in the future, hope not in the far
          752 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
          753 end up with a &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;configuration&lt;/a&gt;
          754 with many things that are interesting for one reason or another, but it is not
          755 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;
          756 &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
          757 Go&lt;/a&gt;, here is
          758 a list of modules that I find neat in NixOS and are not default already. Again,
          759 the list is unordered since this makes it easier to update in the future if I
          760 find something else, but also I don't want to think too hard about an order
          761 here.&lt;/p&gt;
          762 &lt;p&gt;With all above, let's start.&lt;/p&gt;
          763 &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;
          764 &lt;p&gt;&lt;a href="https://www.nftables.org/"&gt;nftables&lt;/a&gt; is, accordingly to Wikipedia:&lt;/p&gt;
          765 &lt;blockquote&gt;
          766 &lt;p&gt;nftables is a subsystem of the Linux kernel providing filtering and
          767 classification of network packets/datagrams/frames.&lt;/p&gt;
          768 &lt;/blockquote&gt;
          769 &lt;p&gt;It is basically a replacement of the venerable
          770 &lt;a href="https://en.wikipedia.org/wiki/Iptables"&gt;iptables&lt;/a&gt;, that still exist and is the
          771 default program to configure the famous &lt;code&gt;networking.firewall&lt;/code&gt;, the declarative
          772 &lt;a href="https://wiki.nixos.org/wiki/Firewall"&gt;Firewall&lt;/a&gt; that NixOS enable by default.&lt;/p&gt;
          773 &lt;p&gt;To enable, it is simple, just add to your configuration:&lt;/p&gt;
          774 &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;
          775 &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;
          776 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          777 &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
          778 still work as expect: any service that you set &lt;code&gt;openFirewall = true&lt;/code&gt; will still
          779 have its ports open, if you set &lt;code&gt;networking.firewall.allowPing = false&lt;/code&gt; it will
          780 still disable pings like before, etc.&lt;/p&gt;
          781 &lt;p&gt;If you look at the documentation of the above option, you will find the
          782 following warning:&lt;/p&gt;
          783 &lt;blockquote&gt;
          784 &lt;p&gt;Note that if you have Docker enabled you will not be able to use nftables
          785 without intervention. Docker uses iptables internally to setup NAT for
          786 containers. This module disables the ip_tables kernel module, however Docker
          787 automatically loads the module. Please see
          788 &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
          789 more information.&lt;/p&gt;
          790 &lt;/blockquote&gt;
          791 &lt;p&gt;I don't use Docker (switched to Podman instead for quite a long time), so I
          792 don't know how bad the situation is. Also keep in mind that &lt;code&gt;nftables&lt;/code&gt; does
          793 offer &lt;code&gt;iptables-compat&lt;/code&gt; for compatibility with old iptables scripts, so it is
          794 most likely Docker doing something weird here.&lt;/p&gt;
          795 &lt;p&gt;Now, the actual advantage from the user here is not clear: the main advantage
          796 from my point of view (and the reason I used to use in other distros like Arch)
          797 is the improved syntax, however if you are using the declarative NixOS's
          798 Firewall you are not interacting with either &lt;code&gt;iptables&lt;/code&gt; or &lt;code&gt;nftables&lt;/code&gt; directly
          799 anyway. &lt;code&gt;nftables&lt;/code&gt; is supposed to be more efficient, but not sure most users
          800 will care about this.&lt;/p&gt;
          801 &lt;p&gt;However if you are the kind of person that needs custom rules, switching to
          802 &lt;code&gt;nftables&lt;/code&gt; does bring a few benefits, including
          803 &lt;code&gt;networking.nftables.checkRuleset&lt;/code&gt; (enabled by default), that checks if your
          804 ruleset has syntax errors during build time. Really valuable to avoid issues
          805 only after switch.&lt;/p&gt;
          806 &lt;p&gt;Anyway, this is one of those options that I think it should be the default for
          807 a long time, since most of the new development in NixOS firewall seems to be
          808 focusing &lt;code&gt;nftables&lt;/code&gt; for a while.&lt;/p&gt;
          809 &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;
          810 &lt;p&gt;&lt;a href="https://github.com/NixOS/nixpkgs/pull/308801"&gt;This one&lt;/a&gt; I just discovered
          811 today, but it has been available for a while (~2 months if you're using
          812 &lt;code&gt;nixos-unstable&lt;/code&gt;). Finally someone is rewriting
          813 &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;,
          814 the Perl script that is called everytime you run &lt;code&gt;nixos-rebuild switch&lt;/code&gt;.&lt;/p&gt;
          815 &lt;p&gt;Now, I am not one of those &amp;quot;rewrite in Rust&amp;quot; zealots, but in this case this is
          816 definitely worth it: &lt;code&gt;switch-to-configuration.pl&lt;/code&gt; is one of those pieces of
          817 code in NixOS that most people avoid touching at the fear of breaking
          818 something. There is a reason why
          819 &lt;a href="https://github.com/NixOS/nixpkgs/commit/eeb2588a59c938042b74183ce1da7052a6ef7e59"&gt;&lt;code&gt;nixos-rebuild&lt;/code&gt;&lt;/a&gt;
          820 is as convoluted as it is, because even if it is a messy shell script, most
          821 people preferred to workaround issues from the &lt;code&gt;switch-to-configuration.pl&lt;/code&gt;
          822 inside it than trying to understand the mess that &lt;code&gt;switch-to-configuration.pl&lt;/code&gt;
          823 is.&lt;/p&gt;
          824 &lt;p&gt;Trying this one is easy:&lt;/p&gt;
          825 &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;
          826 &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;
          827 &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;
          828 &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;
          829 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
          830 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          831 &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
          832 is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
          833 &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
          834 and updated by e.g.: image upgrades instead of modifying root.&lt;/p&gt;
          835 &lt;p&gt;Enabling &lt;code&gt;switch-to-configuration-ng&lt;/code&gt; right now is mostly for testing purposes,
          836 but one of the advantages that I saw is that system switches are (slightly)
          837 faster:&lt;/p&gt;
          838 &lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;sudo nixos-rebuild switch&amp;quot; # switch-to-configuration.pl
          839 Benchmark 1: sudo nixos-rebuild switch
          840   Time (mean ± σ):      3.576 s ±  0.035 s    [User: 0.004 s, System: 0.014 s]
          841   Range (min … max):    3.522 s …  3.645 s    10 runs
          842 &lt;/code&gt;&lt;p&gt;&lt;code&gt;$ hyperfine &amp;quot;sudo nixos-rebuild switch&amp;quot; # switch-to-configuration-ng
          843 Benchmark 1: sudo nixos-rebuild switch
          844 Time (mean ± σ):      3.394 s ±  0.080 s    [User: 0.004 s, System: 0.013 s]
          845 Range (min … max):    3.325 s …  3.608 s    10 runs
          846 &lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
          847 &lt;p&gt;But yes, the difference is not enough to make a significant impact, and it is
          848 not the objective anyway. The real reason for the rewrite is to make it easier
          849 to collaborate. I hope one day we also have someone brave enough to rewrite the
          850 &lt;code&gt;nixos-rebuild&lt;/code&gt; script in something saner.&lt;/p&gt;
          851 &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;
          852 &lt;p&gt;A quick recap on how a modern Linux distro generally boots: the first thing
          853 that the bootloader (say &lt;a href="https://www.gnu.org/software/grub/"&gt;GRUB&lt;/a&gt; or
          854 &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
          855 ramdisk&lt;/em&gt;), a small image that runs from RAM and includes the Linux kernel and
          856 some utilities that are responsible for setting up the main system. For
          857 example, one of the responsibilities of the &lt;code&gt;initrd&lt;/code&gt; is to mount the disks and
          858 start init system (&lt;code&gt;systemd&lt;/code&gt;).&lt;/p&gt;
          859 &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
          860 &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
          861 minimal runtime (e.g.: &lt;a href="https://www.busybox.net/"&gt;busybox&lt;/a&gt;), however &lt;code&gt;systemd&lt;/code&gt;
          862 can also do this job since a long time ago. It is just the role of the distros
          863 to integrate &lt;code&gt;systemd&lt;/code&gt; inside the &lt;code&gt;initrd&lt;/code&gt;.&lt;/p&gt;
          864 &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;.
          865 It make a few subtle changes:&lt;/p&gt;
          866 &lt;ul&gt;
          867 &lt;li&gt;If you are using &lt;a href="https://wiki.nixos.org/wiki/Full_Disk_Encryption"&gt;Full Disk Encryption via
          868 LUKS&lt;/a&gt;, you will get a
          869 different password prompt at login&lt;/li&gt;
          870 &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
          871 boot time
          872 &lt;ul&gt;
          873 &lt;li&gt;You can get even more information (bootloader) if you also use
          874 &lt;code&gt;systemd-boot&lt;/code&gt;&lt;/li&gt;
          875 &lt;/ul&gt;
          876 &lt;/li&gt;
          877 &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
          878 only afterwards)&lt;/li&gt;
          879 &lt;/ul&gt;
          880 &lt;p&gt;But I think the main reason is that since &lt;code&gt;systemd&lt;/code&gt; is event-driven, it should
          881 make boot more reliable, especially in challenging situations (like booting
          882 from network). I can't say that I have any system like this to test if it is
          883 actually more reliable or not, but I don't remember having any issues since I
          884 set &lt;code&gt;boot.initrd.systemd.enable = true&lt;/code&gt;, so there is that.&lt;/p&gt;
          885 &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;
          886 &lt;p&gt;If there is something in that list that has a good chance that you're using
          887 already, it is this one, especially if you're using
          888 &lt;a href="https://wayland.freedesktop.org/"&gt;Wayland&lt;/a&gt;. Still, I think it is interesting
          889 to include in this list since &lt;a href="https://www.pipewire.org/"&gt;PipeWire&lt;/a&gt; is great.&lt;/p&gt;
          890 &lt;p&gt;The experience with PipeWire until now for me was seamless: I never had any
          891 issues with it, all my applications still work exactly as it always worked. I
          892 also didn't had any issues with
          893 &lt;a href="https://www.freedesktop.org/wiki/Software/PulseAudio/"&gt;PulseAudio&lt;/a&gt; for a
          894 while, but I still remember when I first tried PulseAudio during the 0.x in
          895 Fedora and having tons of issues. So bonus points for PipeWire developers for
          896 polishing the experience of enough that most people will feel no difference.&lt;/p&gt;
          897 &lt;p&gt;To enable PipeWire, I would recommend:&lt;/p&gt;
          898 &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;
          899 &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;
          900 &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;
          901 &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;
          902 &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;
          903 &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;
          904 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
          905 &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;
          906 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          907 &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
          908 compatibility with desktop applications (you can also enable
          909 &lt;a href="https://jackaudio.org/"&gt;&lt;code&gt;jack&lt;/code&gt;&lt;/a&gt; if you use professional audio applications).
          910 It also enables &lt;a href="https://github.com/heftig/rtkit"&gt;&lt;code&gt;rtkit&lt;/code&gt;&lt;/a&gt;, allowing PipeWire
          911 to get (soft) realtime, helping avoiding cracks during high CPU load.&lt;/p&gt;
          912 &lt;p&gt;I also recommend taking a look at the &lt;a href="https://wiki.nixos.org/wiki/PipeWire"&gt;Wiki
          913 article&lt;/a&gt;, that has multiple interesting
          914 configurations that can be added for low-latency setups or improved codecs for
          915 Bluetooth devices.&lt;/p&gt;
          916 &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;
          917 &lt;p&gt;There is a good change that you're using
          918 &lt;a href="https://www.networkmanager.dev/"&gt;&lt;code&gt;NetworkManager&lt;/code&gt;&lt;/a&gt; to manage network,
          919 especially for Wi-Fi. And if that is the case, I can't recommend enough
          920 changing the backend from the default &lt;code&gt;wpa_supplicant&lt;/code&gt; to
          921 &lt;a href="https://iwd.wiki.kernel.org/"&gt;&lt;code&gt;iwd&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
          922 &lt;p&gt;If you think that your Wi-Fi takes a long time to connect/re-connect, it may be
          923 because &lt;code&gt;wpa_supplicant&lt;/code&gt;. &lt;code&gt;iwd&lt;/code&gt; seems much more optimised in this regard, and
          924 since switching to it I never felt that my Wi-Fi was worse than other OSes (and
          925 generally slightly better than Windows, but keep in mind that this is a
          926 non-scientific comparison).&lt;/p&gt;
          927 &lt;p&gt;Not saying that I never had Wi-Fi issues since switching to &lt;code&gt;iwd&lt;/code&gt;, however
          928 switching back to &lt;code&gt;wpa_supplicant&lt;/code&gt; in those cases never fixed the issue (it was
          929 the same or worse), so I assume either bad hardware or drivers in those cases.&lt;/p&gt;
          930 &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;
          931 &lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
          932 &lt;p&gt;Most Linux distro nowadays mount &lt;code&gt;/tmp&lt;/code&gt; inside
          933 &lt;a href="https://en.wikipedia.org/wiki/Tmpfs"&gt;tmpfs&lt;/a&gt;, storing the files inside RAM (and
          934 making them actually temporary, e.g.: does not persist between reboots). There
          935 is a &lt;a href="https://lwn.net/Articles/499410/"&gt;long discussion&lt;/a&gt; whether this makes
          936 sense or not, but it is a fact that using &lt;code&gt;/tmp&lt;/code&gt; as an storage for small files
          937 generally makes sense.&lt;/p&gt;
          938 &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
          939 this is because Nix, by default, still builds everything in &lt;code&gt;TMPDIR&lt;/code&gt; (that
          940 defaults to &lt;code&gt;/tmp&lt;/code&gt; if not set). To fix the situation you can do:&lt;/p&gt;
          941 &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;
          942 &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;
          943 &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;
          944 &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;
          945 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
          946 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          947 &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
          948 for large files.&lt;/p&gt;
          949 &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
          950 least setting &lt;code&gt;boot.tmp.cleanOnBoot = true&lt;/code&gt;. This is to avoid issues with e.g.:
          951 mistakenly writing a program that depends in &lt;code&gt;/tmp&lt;/code&gt; being persistent, because
          952 this is the behavior that most other distros expects.&lt;/p&gt;
          953 &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;
          954 &lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
          955 &lt;p&gt;Have a system with small amounts of RAM? &lt;del&gt;You can download more RAM for free,
          956 just click this &lt;a href="https://downloadmoreram.com/"&gt;link&lt;/a&gt;.&lt;/del&gt; Well, just kidding, but
          957 you can actually improve RAM usage by compressing it. This is what
          958 &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
          959 device using part of your RAM that will be compressed.&lt;/p&gt;
          960 &lt;p&gt;While this means that whatever is in the compressed part can't be used as-is
          961 (it needs to be decompressed first), it means you can store a lot more
          962 information. And since RAM is much faster than disk, this generally is a good
          963 trade-off. It is still recommended that you have some swap space though, e.g.:
          964 to swap pages that are unused.&lt;/p&gt;
          965 &lt;p&gt;To enable it, you can do:&lt;/p&gt;
          966 &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;
          967 &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;
          968 &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;
          969 &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;
          970 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
          971 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          972 &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
          973 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
          974 fast, since you probably want to minimise CPU usage. Well, unless you have a
          975 very good CPU and small amounts of RAM, them it may make sense to use an
          976 algorithm that compress better but uses more CPU.&lt;/p&gt;
          977 &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;
          978 &lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
          979 &lt;p&gt;Do you have a SSD? If so, you probably want to
          980 &lt;a href="https://en.wikipedia.org/wiki/Trim_(computing)"&gt;trim&lt;/a&gt; it regularly to keep a
          981 good performance. This should be as easy as:&lt;/p&gt;
          982 &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;
          983 &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;
          984 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          985 &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
          986 need to enable &lt;code&gt;boot.initrd.luks.devices.&amp;lt;name&amp;gt;.allowDiscards&lt;/code&gt;, otherwise
          987 &lt;code&gt;fstrim&lt;/code&gt; will not work. The reason that this is not enabled by default is
          988 because there are some &lt;a href="https://asalor.blogspot.com/2011/08/trim-dm-crypt-problems.html"&gt;security
          989 implications&lt;/a&gt;.
          990 It shouldn't be an issue for most people, but if you are paranoid please assess
          991 your risks first.&lt;/p&gt;
          992 &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;
          993 &lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
          994 &lt;p&gt;This is one of my favorites. Do you want to have the power to run binaries from
          995 other architectures like
          996 &lt;a href="https://en.wikipedia.org/wiki/ARM_architecture_family"&gt;ARM&lt;/a&gt; or
          997 &lt;a href="https://en.wikipedia.org/wiki/RISC-V"&gt;RISC-V&lt;/a&gt;? In NixOS it is easy:&lt;/p&gt;
          998 &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;
          999 &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;
         1000 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1001 &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
         1002 &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
         1003 automatically detect when a binary from another architecture is run,
         1004 running it with the QEMU emulator instead.&lt;/p&gt;
         1005 &lt;p&gt;Now, keep in mind that you still need e.g.: libraries for the target
         1006 architecture. However this is where the Nix magic comes in, you can easily
         1007 compile something to another architecture as:&lt;/p&gt;
         1008 &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
         1009 &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
         1010 &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
         1011 &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
         1012 &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
         1013 &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
         1014 &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
         1015 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, world!
         1016 &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
         1017 architecture, everything works as expect.&lt;/p&gt;
         1018 &lt;p&gt;You also need to temper your expectations: QEMU emulation is slow. It is
         1019 sufficient fast to build small programs, but if you need to build something
         1020 bigger, I would recommend you to do it via &lt;a href="https://kokada.dev/blog/building-static-binaries-in-nix"&gt;cross-compilation
         1021 instead&lt;/a&gt;.&lt;/p&gt;
         1022 &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;
         1023 &lt;p&gt;&lt;em&gt;Added in 2024-08-22&lt;/em&gt;&lt;/p&gt;
         1024 &lt;p&gt;Someone in
         1025 &lt;a href="https://lobste.rs/s/ymmale/unordered_list_hidden_gems_inside_nixos#c_dz1hck"&gt;Lobste.rs&lt;/a&gt;
         1026 remembered me of this gem: &lt;a href="https://github.com/bus1/dbus-broker"&gt;dbus-broker&lt;/a&gt;,
         1027 a high performance implementation of
         1028 &lt;a href="https://en.wikipedia.org/wiki/D-Bus"&gt;D-Bus&lt;/a&gt;.&lt;/p&gt;
         1029 &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
         1030 Communication&lt;/a&gt;
         1031 protocol used mostly in Linux desktop. And distros like
         1032 &lt;a href="https://archlinux.org/news/making-dbus-broker-our-default-d-bus-daemon/"&gt;Arch&lt;/a&gt;
         1033 and
         1034 &lt;a href="https://fedoraproject.org/wiki/Changes/DbusBrokerAsTheDefaultDbusImplementation"&gt;Fedora&lt;/a&gt;
         1035 already use it to default, so there aren't lots of reason to not use it.&lt;/p&gt;
         1036 &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;
         1037 &lt;p&gt;&lt;em&gt;Added in 2024-08-23&lt;/em&gt;&lt;/p&gt;
         1038 &lt;p&gt;Here is one that I have no experience but got curious after this comment in
         1039 &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;
         1040 &lt;blockquote&gt;
         1041 &lt;p&gt;One more option to add is maybe &lt;code&gt;services.irqbalance.enable&lt;/code&gt;. For my old
         1042 laptop it’s a life savior: no more freezes of the DE or video playback during
         1043 heavy operations like e.g. compilation.&lt;/p&gt;
         1044 &lt;/blockquote&gt;
         1045 &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
         1046 that you write for yourself. This blog post is the latter kind. What I am going
         1047 to talk here is probably something most people know, but I didn't, and the
         1048 documentation is all scattered in the internet. So I am writing mostly to
         1049 remember myself, in future, if I have the same kind of issue what I need to do.&lt;/p&gt;
         1050 &lt;p&gt;The context: &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt;, the blog platform
         1051 &lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; is hosted on, relies in
         1052 &lt;a href="https://python-markdown.github.io/"&gt;python-markdown&lt;/a&gt;, predates
         1053 &lt;a href="https://commonmark.org/"&gt;CommonMark&lt;/a&gt; specification and as such, has some
         1054 incompatibilities. One of those incompatibilities with CommonMark is the way
         1055 sub lists are handled. From the documentation:&lt;/p&gt;
         1056 &lt;blockquote&gt;
         1057 &lt;p&gt;The syntax rules clearly state that when a list item consists of multiple
         1058 paragraphs, “each subsequent paragraph in a list item must be indented by
         1059 either 4 spaces or one tab” (emphasis added). However, many implementations
         1060 do not enforce this rule and allow less than 4 spaces of indentation. The
         1061 implementers of Python-Markdown consider it a bug to not enforce this rule.&lt;/p&gt;
         1062 &lt;/blockquote&gt;
         1063 &lt;p&gt;CommonMark &lt;a href="https://spec.commonmark.org/0.31.2/#lists"&gt;relax those
         1064 restrictions&lt;/a&gt;, allowing a sublist to
         1065 be defined with just 2 spaces of indentation.&lt;/p&gt;
         1066 &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
         1067 Go&lt;/a&gt; and
         1068 a CommonMark renderer called &lt;a href="https://github.com/yuin/goldmark/"&gt;Goldmark&lt;/a&gt;. I
         1069 them re-render the Markdown to Mataroa using a &lt;a href="https://github.com/teekennedy/goldmark-markdown"&gt;Markdown
         1070 renderer&lt;/a&gt; before publising to
         1071 &lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt;, because this allow me to do some
         1072 transformations in the original Markdown. It mostly works fine except for sub
         1073 lists, thanks to the fact that the Markdown renderer I am using renders sub
         1074 lists with 2 spaces.&lt;/p&gt;
         1075 &lt;p&gt;The only reason sub lists are working right now is because
         1076 &lt;a href="https://gluer.org/"&gt;@ratsclub&lt;/a&gt; fixed this issue in the fork that
         1077 &lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; runs. But I want to be compatible with
         1078 the official instance if I ever need to migrate.&lt;/p&gt;
         1079 &lt;p&gt;The solution? Let's fix this in a
         1080 &lt;a href="https://github.com/teekennedy/goldmark-markdown/pull/21"&gt;PR&lt;/a&gt;. However now that
         1081 I have code to fix the issue, how can I use it without waiting upstream to
         1082 merge my code?&lt;/p&gt;
         1083 &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;
         1084 directive&lt;/a&gt;:&lt;/p&gt;
         1085 &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;
         1086 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1087 &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;
         1088 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1089 &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;
         1090 &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;
         1091 &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;
         1092 &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;
         1093 &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;
         1094 &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;
         1095 &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;
         1096 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         1097 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1098 &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;
         1099 &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
         1100 &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
         1101 &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
         1102 &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;
         1103 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1104 &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;
         1105 &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
         1106 fork in &lt;code&gt;github.com/thiagokokada/goldmark-markdown&lt;/code&gt;. You even get all the
         1107 reproducibility of modules since Go automatically pins the commit.&lt;/p&gt;
         1108 &lt;p&gt;Since the Go format for versions is quite unique, you can just set to the
         1109 desired branch (e.g.: instead of &lt;code&gt;v0.0.0-20240820111219-f30775d8ed15&lt;/code&gt;, you can
         1110 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
         1111 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
         1112 &lt;a href="https://github.com/elliotchance/orderedmap/"&gt;elliotchance/orderedmap&lt;/a&gt; as my
         1113 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
         1114 one&lt;/a&gt; in
         1115 standard library yet). I recently did a
         1116 &lt;a href="https://github.com/elliotchance/orderedmap/pull/41"&gt;PR&lt;/a&gt; to implement Go 1.23
         1117 iterators, because I find them neat, however I was avoiding to use it in the
         1118 code that generates this &lt;a href="https://github.com/thiagokokada/blog"&gt;blog&lt;/a&gt; since Go
         1119 1.23 was just released and is still not the default Go in
         1120 &lt;a href="https://github.com/NixOS/nixpkgs"&gt;nixpkgs&lt;/a&gt;.&lt;/p&gt;
         1121 &lt;p&gt;I decided that I would create a
         1122 &lt;a href="https://github.com/thiagokokada/blog/pull/2"&gt;branch&lt;/a&gt; and leave there for a few
         1123 months, until I decided to try to run the code locally and got this:&lt;/p&gt;
         1124 &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
         1125 &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)
         1126 &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;
         1127 &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
         1128 &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
         1129 &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
         1130 &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
         1131 page&lt;/a&gt;:&lt;/p&gt;
         1132 &lt;blockquote&gt;
         1133 &lt;p&gt;Starting in Go 1.21, the Go distribution consists of a go command and a
         1134 bundled Go toolchain, which is the standard library as well as the compiler,
         1135 assembler, and other tools. The go command can use its bundled Go toolchain
         1136 as well as other versions that it finds in the local PATH or downloads as
         1137 needed.&lt;/p&gt;
         1138 &lt;/blockquote&gt;
         1139 &lt;p&gt;There are a bunch of rules here that I am not going to enter in detail (I
         1140 recommend you to read the official documentation), but a quick summary:&lt;/p&gt;
         1141 &lt;ul&gt;
         1142 &lt;li&gt;
         1143 &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;
         1144 is set to a Go version higher than your current &lt;code&gt;go&lt;/code&gt; binary&lt;/p&gt;
         1145 &lt;ul&gt;
         1146 &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
         1147 version that introduces this behavior&lt;/li&gt;
         1148 &lt;/ul&gt;
         1149 &lt;/li&gt;
         1150 &lt;li&gt;
         1151 &lt;p&gt;You can force a specific toolchain with &lt;code&gt;GOTOOLCHAIN&lt;/code&gt; environment setting,
         1152 e.g.: &lt;code&gt;GOTOOLCHAIN=1.23&lt;/code&gt;&lt;/p&gt;
         1153 &lt;ul&gt;
         1154 &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
         1155 behavior described in this post&lt;/li&gt;
         1156 &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
         1157 previous behaviour pre-1.21 Go&lt;/li&gt;
         1158 &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
         1159 docs&lt;/li&gt;
         1160 &lt;/ul&gt;
         1161 &lt;/li&gt;
         1162 &lt;li&gt;
         1163 &lt;p&gt;The downloaded toolchains go to whatever your &lt;code&gt;GOPATH&lt;/code&gt; is, inside
         1164 &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
         1165 example:&lt;/p&gt;
         1166 &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
         1167 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;total 64
         1168 &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 .
         1169 &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 ..
         1170 &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
         1171 &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
         1172 &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
         1173 &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
         1174 &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
         1175 &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
         1176 &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
         1177 &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
         1178 &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
         1179 &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
         1180 &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
         1181 &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
         1182 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
         1183 &lt;/ul&gt;
         1184 &lt;p&gt;By the way, this only works well because Go toolchain binaries are static.&lt;/p&gt;
         1185 &lt;p&gt;While I don't like a program downloading random binaries from the internet, I
         1186 like what Go is doing here. It makes the whole bootstrapping process for a Go
         1187 project much easier: as long as you have a reasonable up-to-date &lt;code&gt;go&lt;/code&gt; binary in
         1188 your &lt;code&gt;PATH&lt;/code&gt;, you should be ready to go (pun intended). And Go modules are
         1189 already reasonable secure, ensuring that each module have a proper checksum. As
         1190 long as nobody else can publish modules in &lt;code&gt;golang.org/toolchain&lt;/code&gt; namespace I
         1191 can't see much of a security issue here, but I am not a security expert.&lt;/p&gt;
         1192 &lt;p&gt;But if you don't like this behavior, you can always disable it by setting
         1193 &lt;code&gt;GOTOOLCHAIN=local&lt;/code&gt;. And just do not forget to set this in your
         1194 &lt;a href="https://brandur.org/fragments/go-version-matrix"&gt;CI&lt;/a&gt;, unless you don't care
         1195 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
         1196 language&lt;/a&gt;, and has some
         1197 good qualities that makes up for its flaws. However, this doesn't mean I think
         1198 the language couldn't be better, far from it.&lt;/p&gt;
         1199 &lt;p&gt;This blog post is a list of things that I miss from Go from other languages.
         1200 Some of the things here could probably be implemented soon, some other would
         1201 probably need a major revision of the language. The list is unordered, because
         1202 this makes it easier for me to update in the future if I found something else,
         1203 but also because I don't want to think too hard about giving each point here a
         1204 rank.&lt;/p&gt;
         1205 &lt;p&gt;With all above, let's start.&lt;/p&gt;
         1206 &lt;h2&gt;Ordered maps in standard library&lt;/h2&gt;
         1207 &lt;p&gt;When I first learned about
         1208 &lt;a href="https://docs.python.org/3/library/stdtypes.html#typesmapping"&gt;dictionaries&lt;/a&gt; in
         1209 Python it quickly became one of my favorite data structures ever. They're
         1210 extremely versatile, and most modern programming languages have something
         1211 similar in its standard library. Go isn't different, it has
         1212 &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
         1213 table&lt;/a&gt;. However &lt;code&gt;map&lt;/code&gt; in Go are
         1214 quirky, for example:&lt;/p&gt;
         1215 &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;
         1216 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1217 &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;
         1218 &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;
         1219 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1220 &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;
         1221 &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;
         1222 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         1223 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1224 &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
         1225 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bar
         1226 &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
         1227 &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
         1228 &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
         1229 &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
         1230 &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;
         1231 &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
         1232 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo
         1233 &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
         1234 &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
         1235 &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
         1236 &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
         1237 &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;
         1238 &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
         1239 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;qux
         1240 &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
         1241 &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
         1242 &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
         1243 &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
         1244 &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
         1245 elements, but Go actually &lt;a href="https://victoriametrics.com/blog/go-map/"&gt;randomise each map
         1246 instance&lt;/a&gt;:&lt;/p&gt;
         1247 &lt;blockquote&gt;
         1248 &lt;p&gt;But here’s the deal, while the hash function used for maps in Go is
         1249 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
         1250 that hash function is different for each map instance. So, when you create a
         1251 new map, Go generates a random seed just for that map.&lt;/p&gt;
         1252 &lt;/blockquote&gt;
         1253 &lt;p&gt;While I understand the reason for this (i.e.: to avoid developers relying in a
         1254 specific iteration order), I still find it weird, and I think this is something
         1255 unique for Go. This decision means that even if you don't care about a specific
         1256 order, you will still need to sort the map before doing something else if you
         1257 want reproducibility, something that I care a lot.&lt;/p&gt;
         1258 &lt;p&gt;The fix for this? Go could offer an ordered map implementation inside the
         1259 standard library. An ordered map ensure that the iteration order of the map is
         1260 the same as the insertion order (that is, by the way, a powerful property that
         1261 allow maps to be used in other contexts, not just my pet peeve above).&lt;/p&gt;
         1262 &lt;p&gt;Python actually does this for any dictionaries since &lt;a href="https://stackoverflow.com/a/39980744"&gt;Python
         1263 3.6&lt;/a&gt;, but it offered an
         1264 &lt;a href="https://docs.python.org/3/library/collections.html#collections.OrderedDict"&gt;OrderedDict&lt;/a&gt;
         1265 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,
         1266 that maybe useful in specific cases).&lt;/p&gt;
         1267 &lt;p&gt;Before generics it would be impossible to have a type-safe API for such data
         1268 structure without introducing a new data type in the language (like &lt;code&gt;slices&lt;/code&gt;),
         1269 but now Go has generics so it is not an issue anymore. The other issue is that
         1270 you would be forced to iterate manually in this new data structure, but thanks
         1271 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
         1272 Go 1.23, it means we can iterate in an ordered map as a library almost exactly
         1273 like we can do as a &lt;code&gt;map&lt;/code&gt;:&lt;/p&gt;
         1274 &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;
         1275 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1276 &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;
         1277 &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;
         1278 &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;
         1279 &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;
         1280 &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;
         1281 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1282 &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;
         1283 &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
         1284 &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;
         1285 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1286 &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
         1287 with third party implementations, e.g.: I am using this
         1288 &lt;a href="https://github.com/elliotchance/orderedmap"&gt;one&lt;/a&gt; in one of my projects. But
         1289 being in standard library reduces the friction: if there was some
         1290 implementation in standard library, I would generally prefer it unless I have
         1291 some specific needs. However when the standard library doesn't offer what I
         1292 need, I need to find it myself a suitable library, and this ends up taking time
         1293 since generally there are lots of alternatives.&lt;/p&gt;
         1294 &lt;h2&gt;Keyword and default arguments for functions&lt;/h2&gt;
         1295 &lt;p&gt;Something that comes straight from Python that I miss sometimes in Go is that
         1296 you can do things like this when declaring a function:&lt;/p&gt;
         1297 &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;
         1298 &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;
         1299 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1300 &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;
         1301 &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;
         1302 &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;
         1303 &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
         1304 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hello, Foo
         1305 &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
         1306 &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
         1307 &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
         1308 for Go standard library, for example, &lt;code&gt;string.Replace&lt;/code&gt;:&lt;/p&gt;
         1309 &lt;blockquote&gt;
         1310 &lt;p&gt;&lt;code&gt;func Replace(s, old, new string, n int) string&lt;/code&gt;&lt;/p&gt;
         1311 &lt;p&gt;Replace returns a copy of the string s with the first n non-overlapping
         1312 instances of old replaced by new. If old is empty, it matches at the
         1313 beginning of the string and after each UTF-8 sequence, yielding up to k+1
         1314 replacements for a k-rune string. If n &amp;lt; 0, there is no limit on the number
         1315 of replacements.&lt;/p&gt;
         1316 &lt;/blockquote&gt;
         1317 &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
         1318 &lt;code&gt;strings.ReplaceAll&lt;/code&gt; (since this is basically what this function does, call
         1319 &lt;code&gt;strings.Replace(s, old, new, -1)&lt;/code&gt;).&lt;/p&gt;
         1320 &lt;h2&gt;Nullability (or nillability)&lt;/h2&gt;
         1321 &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
         1322 Go&lt;/a&gt;, but I want to
         1323 expand here.&lt;/p&gt;
         1324 &lt;p&gt;First, I don't think the language needs to support the generic solution for
         1325 nullability, that would be either having proper Union or Sum types. Kotlin
         1326 AFAIK doesn't support neither, but my 2 years experience with Kotlin showed
         1327 that just having nullable types already helped a lot in ensuring type safety.&lt;/p&gt;
         1328 &lt;p&gt;Second, I do feel that Go has less issues with &lt;code&gt;nil&lt;/code&gt; values, than say, Java,
         1329 because its decision of using zero values instead of &lt;code&gt;nil&lt;/code&gt; in many cases. So
         1330 for example, a string can never be &lt;code&gt;nil&lt;/code&gt;, however a string pointer can be. This
         1331 means that this is fine:&lt;/p&gt;
         1332 &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;
         1333 &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
         1334 &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;
         1335 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However:&lt;/p&gt;
         1336 &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;
         1337 &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
         1338 &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;
         1339 &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
         1340 languages that offer nullables (heck, even Python with
         1341 &lt;a href="https://www.mypy-lang.org/"&gt;&lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt; is safer).&lt;/p&gt;
         1342 &lt;p&gt;Sadly this is the change in this post that is more likely to need a completely
         1343 new revision of the language.
         1344 &lt;a href="https://github.com/golang/go/issues/49202"&gt;nillability&lt;/a&gt; was proposed before,
         1345 but it is really unlikely it can be done without breaking backwards
         1346 compatibility.&lt;/p&gt;
         1347 &lt;p&gt;It could be done the Java way by adding a &lt;code&gt;nullable&lt;/code&gt; type to the standard
         1348 library (&lt;a href="https://jcp.org/en/jsr/detail?id=305"&gt;JSR305&lt;/a&gt;), but the fact that
         1349 &lt;a href="https://stackoverflow.com/questions/2289694/what-is-the-status-of-jsr-305"&gt;JSR305 is considerd
         1350 dead&lt;/a&gt;
         1351 by many shows how difficult it is to do something like this without a major
         1352 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
         1353 successfully&lt;/a&gt;, but
         1354 definitely it was not without its pains. And the fact that most people that
         1355 program in Dart probably does because of Flutter (that eventually required
         1356 newer versions with null-safety) is not a good sign.&lt;/p&gt;
         1357 &lt;h2&gt;Lambdas&lt;/h2&gt;
         1358 &lt;p&gt;&lt;em&gt;Added in 2024-08-18&lt;/em&gt;&lt;/p&gt;
         1359 &lt;p&gt;Go is a surprising good language for some functional code, thanks to having
         1360 first class functions and closures. Sadly the syntax doesn't help, since the
         1361 only way you can use anonymous functions in Go is using &lt;code&gt;func&lt;/code&gt;. Especially if
         1362 the types are complex, this can result in some convoluted code. Take the
         1363 example from the &lt;a href="https://go.dev/wiki/RangefuncExperiment"&gt;&lt;code&gt;range-over-func&lt;/code&gt;
         1364 experiment&lt;/a&gt;:&lt;/p&gt;
         1365 &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;
         1366 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1367 &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;
         1368 &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;
         1369 &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;
         1370 &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;
         1371 &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;
         1372 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
         1373 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         1374 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
         1375 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1376 &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
         1377 could be simplified a lot:&lt;/p&gt;
         1378 &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;
         1379 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1380 &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;
         1381 &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;
         1382 &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;
         1383 &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;
         1384 &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;
         1385 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
         1386 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         1387 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
         1388 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1389 &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
         1390 the types to be elided in an unnamed function:&lt;/p&gt;
         1391 &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;
         1392 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1393 &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;
         1394 &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;
         1395 &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;
         1396 &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;
         1397 &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;
         1398 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
         1399 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         1400 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
         1401 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1402 &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
         1403 future version of the language, since they didn't close the
         1404 &lt;a href="https://github.com/golang/go/issues/21498"&gt;issue&lt;/a&gt; yet, and the discussion
         1405 about the possibility of this feature is still ongoing.&lt;/p&gt;
         1406 &lt;h2&gt;Error on unused return values&lt;/h2&gt;
         1407 &lt;p&gt;&lt;em&gt;Added in 2024-09-12&lt;/em&gt;&lt;/p&gt;
         1408 &lt;p&gt;In a &lt;a href="https://kokada.dev/blog/my-coding-experience-with-llm"&gt;recent post about
         1409 LLMs&lt;/a&gt;, I talked about
         1410 this function that I asked ChatGPT to generate to create a socket that accepted
         1411 a &lt;a href="https://pkg.go.dev/context"&gt;context&lt;/a&gt;:&lt;/p&gt;
         1412 &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;
         1413 &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;
         1414 &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;
         1415 &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;
         1416 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1417 &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
         1418 &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;
         1419 &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;
         1420 &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;
         1421 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}()&lt;/span&gt;
         1422 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1423 &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;
         1424 &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
         1425 &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()
         1426 &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;
         1427 &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
         1428 &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;
         1429 &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
         1430 &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;
         1431 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
         1432 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1433 &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
         1434 post&lt;/a&gt; about &amp;quot;The Error
         1435 Model&amp;quot;, I realised that this function is lacking error check during
         1436 &lt;code&gt;conn.SetReadDeadline()&lt;/code&gt; calls:&lt;/p&gt;
         1437 &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;
         1438 &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;
         1439 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1440 &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
         1441 &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;
         1442 &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;
         1443 &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;
         1444 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}()&lt;/span&gt;
         1445 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1446 &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;
         1447 &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;
         1448 &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;
         1449 &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;
         1450 &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()
         1451 &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;
         1452 &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;
         1453 &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;
         1454 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
         1455 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="c1"&gt;// Reset read deadline
         1456 &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;
         1457 &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;
         1458 &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;
         1459 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        &lt;span class="p"&gt;}&lt;/span&gt;
         1460 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                &lt;span class="p"&gt;}()&lt;/span&gt;
         1461 &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
         1462 &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;
         1463 &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;
         1464 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         1465 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1466 &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
         1467 happening even in a Pull Request done by an actual human.&lt;/p&gt;
         1468 &lt;p&gt;Ignoring errors is bad, especially because once the error is ignored it is
         1469 gone, forever. You may have strange issues in the code that are impossible to
         1470 debug because you can't know about something that doesn't exist. This is one of
         1471 the reasons I think exceptions are better, since this is the kind of error that
         1472 would be impossible to ignore if the language had exceptions, and the exception
         1473 would eventually propagate in your stack until it is captured or you get a
         1474 crash (with a stack trace to debug the issue).&lt;/p&gt;
         1475 &lt;p&gt;Now, I don't think this is a fault of using error as values instead of
         1476 exceptions. Go has the tools to encapsulate the errors and propagate them
         1477 properly, like it is shown in the fixed code. But the fact that someone can do
         1478 this mistake without any warning or error from the compiler is bad.&lt;/p&gt;
         1479 &lt;p&gt;If Go had a warning or error for unused return values, this would be different:&lt;/p&gt;
         1480 &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;
         1481 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;// ...
         1482 &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;
         1483 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1484 &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
         1485 a good reason. I can ask during a Pull Request why the committer is ignoring if
         1486 it is lacking appropriate context, and maybe even ask for some comments to be
         1487 added why this would be safe. What can't happen is this being ignored by
         1488 mistake.&lt;/p&gt;
         1489 &lt;p&gt;I am not completely sure if I want this only for errors or for any unused value
         1490 though. There are lots of cases where ignoring non-error values is ok, but I
         1491 also don't see the issue of having something like:&lt;/p&gt;
         1492 &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;
         1493 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;// ...
         1494 &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;
         1495 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1496 &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
         1497 only calling this function only for its side-effects.&lt;/p&gt;
         1498 &lt;p&gt;By the way, yes, &lt;a href="https://github.com/kisielk/errcheck"&gt;errcheck&lt;/a&gt; linter exists,
         1499 but if the language itself doesn't enforce this it means that there are lots of
         1500 libraries that are probably mistakenly ignoring errors. And if the library is
         1501 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
         1502 &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;
         1503 option does not receive a derivation like most other packages, but instead
         1504 receives a function and returns an attribute set with all packages. Here is the
         1505 documentation:&lt;/p&gt;
         1506 &lt;blockquote&gt;
         1507 &lt;p&gt;This option allows you to override the Linux kernel used by NixOS. Since
         1508 things like external kernel module packages are tied to the kernel you’re
         1509 using, it also overrides those. This option is a function that takes Nixpkgs
         1510 as an argument (as a convenience), and returns an attribute set containing at
         1511 the very least an attribute kernel. Additional attributes may be needed
         1512 depending on your configuration. For instance, if you use the NVIDIA X
         1513 driver, then it also needs to contain an attribute &lt;code&gt;nvidia_x11&lt;/code&gt;.&lt;/p&gt;
         1514 &lt;/blockquote&gt;
         1515 &lt;p&gt;The kernel package itself is referenced by the
         1516 &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;
         1517 derivation inside this attribute set:&lt;/p&gt;
         1518 &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
         1519 &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»
         1520 &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
         1521 with the same kernel version you are booting.&lt;/p&gt;
         1522 &lt;p&gt;However one less obvious consequence about this is that if you want packages
         1523 that come from &lt;code&gt;linuxPackages&lt;/code&gt;, say for example
         1524 &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;
         1525 or
         1526 &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;,
         1527 it is better to do:&lt;/p&gt;
         1528 &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;
         1529 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
         1530 &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;
         1531 &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;
         1532 &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;
         1533 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
         1534 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1535 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of:&lt;/p&gt;
         1536 &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;
         1537 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
         1538 &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;
         1539 &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;
         1540 &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;
         1541 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
         1542 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1543 &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
         1544 those packages depends in a specific kernel version for a reason, so getting
         1545 them from &lt;code&gt;config.boot.kernelPackages&lt;/code&gt; reduces the chance of you having some
         1546 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
         1547 of the workflow in this blog is &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;automated using
         1548 Go&lt;/a&gt;. I
         1549 basically write Markdown files with some special rules inside the
         1550 &lt;a href="https://github.com/thiagokokada/blog"&gt;repository&lt;/a&gt;, commit and push it. In
         1551 seconds, the CI (currently &lt;a href="https://github.com/thiagokokada/blog/blob/4e3f25485c6682f3e066b219df2290934bc0d256/.github/workflows/go.yml"&gt;GitHub
         1552 Actions&lt;/a&gt;)
         1553 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
         1554 itself&lt;/a&gt; as a
         1555 backup blog) and publish to the &lt;a href="https://kokada.capivaras.dev/"&gt;capivaras.dev
         1556 website&lt;/a&gt;.&lt;/p&gt;
         1557 &lt;p&gt;Now, considering how much about &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt; I talk in this blog,
         1558 it should be a surprise that the workflow above has &lt;strong&gt;zero&lt;/strong&gt; Nix code inside
         1559 it. I am not saying this blog will never have it, but I am only going to add if
         1560 this is necessary, for example if I start using a tool to build this blog that
         1561 I generally don't expect it to be installed by the machine I am currently
         1562 using. Go is an exception of this rule since it is relatively straightfoward to
         1563 install (just download the &lt;a href="https://go.dev/doc/install"&gt;binary&lt;/a&gt;) and because
         1564 its &lt;a href="https://go.dev/doc/go1compat"&gt;stability guarantee&lt;/a&gt; means (hopefully) no
         1565 breakage. But most other things I consider moving targets, and I wouldn't be
         1566 comfortable to use unless I have Nix to ensure reproducibility.&lt;/p&gt;
         1567 &lt;p&gt;This is why the other tool that this blog (ab)uses during its workflow is
         1568 &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
         1569 build automation tool that exist. It is basically available in any *nix (do not
         1570 confuse with &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt;) system, from most Linux distros to
         1571 macOS, by default. So it is the tool I choose to automatise some tasks in this
         1572 blog, even if I consider writing a &lt;code&gt;Makefile&lt;/code&gt; (the domain-specific language
         1573 that &lt;code&gt;Make&lt;/code&gt; uses) kind of a lost, dark art.&lt;/p&gt;
         1574 &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
         1575 explain some basic concepts, but if you want an actual tutorial a good one can
         1576 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;
         1577 thanks to the reasons above, you can use many other tools for a similar
         1578 objective, like &lt;a href="https://github.com/casey/just"&gt;Justfiles&lt;/a&gt;,
         1579 &lt;a href="https://taskfile.dev/"&gt;Taskfiles&lt;/a&gt; (sadly it uses
         1580 &lt;a href="https://kokada.dev/blog/generating-yaml-files-with-nix"&gt;YAML&lt;/a&gt;), or even a
         1581 small script written in any language you want. The reason that I am writing
         1582 this post is why you should do it, not how.&lt;/p&gt;
         1583 &lt;p&gt;A quick recap on how this blog works: inside the
         1584 &lt;a href="https://github.com/thiagokokada/blog"&gt;repository&lt;/a&gt;, a post is basically a
         1585 Markdown post following the directory structure below
         1586 (&lt;a href="https://github.com/thiagokokada/blog/tree/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7"&gt;permalink&lt;/a&gt;
         1587 for the current version of this blog):&lt;/p&gt;
         1588 &lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;.
         1589 &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;
         1590 &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
         1591 &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
         1592 &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
         1593 &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
         1594 &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
         1595 &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
         1596 &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
         1597 &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
         1598 &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
         1599 &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
         1600 &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
         1601 &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
         1602 &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
         1603 &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
         1604 &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
         1605 &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
         1606 &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
         1607 &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
         1608 &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
         1609 &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
         1610 &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
         1611 &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
         1612 &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
         1613 &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
         1614 &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
         1615 &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,
         1616 that will be automatically extract to be used as the post title, but otherwise
         1617 there is no other formatting rules. It is a highly optionated structure, but
         1618 the nice thing about being optionated is that we can extract lots of
         1619 information just from how the files are organised in the filesystem.&lt;/p&gt;
         1620 &lt;p&gt;Most of the magic that converts those Markdown files to actual blog posts are
         1621 in the Go files that you can see above: &lt;code&gt;blog.go&lt;/code&gt; is the main logic that walks
         1622 in the repository and extracts the necessary information, &lt;code&gt;mataroa.go&lt;/code&gt; is
         1623 responsible for the &lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt; integration (that
         1624 uses &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt; platform), while &lt;code&gt;link_rewriter.go&lt;/code&gt; is
         1625 responsible to do some transformations in the Markdown files before posting.&lt;/p&gt;
         1626 &lt;p&gt;While I could manage everything by just using &lt;code&gt;go&lt;/code&gt; CLI and a few other *nix
         1627 commands, to make it easier to manager everything I have the following
         1628 &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;
         1629 &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;
         1630 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1631 &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;
         1632 &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;
         1633 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1634 &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;.*
         1635 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        go build
         1636 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1637 &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;
         1638 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog &amp;gt; README.md
         1639 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1640 &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;
         1641 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog -rss &amp;gt; rss.xml
         1642 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1643 &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;
         1644 &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;
         1645 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ./blog -publish
         1646 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1647 &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;
         1648 &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;
         1649 &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;
         1650 &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;
         1651 &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;
         1652 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1653 &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;
         1654 &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;
         1655 &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;
         1656 &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;
         1657 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1658 &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;
         1659 &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;
         1660 &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;
         1661 &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;
         1662 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1663 &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;
         1664 &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;
         1665 &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;
         1666 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1667 &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;
         1668 &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;
         1669 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        rm -rf blog
         1670 &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
         1671 &lt;a href="https://en.wikipedia.org/wiki/Make_(software)#Makefile"&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
         1672 &lt;blockquote&gt;
         1673 &lt;p&gt;Each rule begins with a &lt;em&gt;dependency line&lt;/em&gt; which consists of the rule's target
         1674 name followed by a colon (:) and optionally a list of targets on which the
         1675 rule's target depends, its prerequisites.&lt;/p&gt;
         1676 &lt;/blockquote&gt;
         1677 &lt;p&gt;So if we look for example at the &lt;code&gt;blog&lt;/code&gt; binary, the dependencies are all the
         1678 &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
         1679 &lt;code&gt;blog&lt;/code&gt; binary by running:&lt;/p&gt;
         1680 &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
         1681 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
         1682 &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
         1683 has a newer timestamp than the target file, and only trigger the build again if
         1684 there are changes, for example:&lt;/p&gt;
         1685 &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
         1686 &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.
         1687 &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;
         1688 &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
         1689 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
         1690 &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
         1691 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
         1692 &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
         1693 target as &lt;code&gt;.PHONY&lt;/code&gt;, that basically instructs &lt;code&gt;Makefile&lt;/code&gt; to always make the
         1694 target. One classic example is &lt;code&gt;clean&lt;/code&gt; target, that removes build artifacts:&lt;/p&gt;
         1695 &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
         1696 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;rm -rf blog
         1697 &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;
         1698 &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
         1699 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;rm -rf blog
         1700 &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
         1701 dependencies incorrectly, especially in languages that has fast build times
         1702 like e.g.: Go. The worst thing that can happen is something not being rebuild
         1703 when it needs to. So my recomendation if you are writing your first &lt;code&gt;Makefile&lt;/code&gt;
         1704 is to just declare everything as &lt;code&gt;.PHONY&lt;/code&gt;. You can always improve it later.&lt;/p&gt;
         1705 &lt;p&gt;One last basic concept that I want to explain about &lt;code&gt;Makefile&lt;/code&gt; is the default
         1706 target: it is the target that is run if you just run &lt;code&gt;make&lt;/code&gt; without arguments
         1707 inside the directory that contains a &lt;code&gt;Makefile&lt;/code&gt;. The default target is
         1708 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;
         1709 target (that is also marked as &lt;code&gt;.PHONY&lt;/code&gt;) that has as dependencies all the
         1710 targets that you want to build by default. In this particular case I declare
         1711 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
         1712 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;
         1713 &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
         1714 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;go build
         1715 &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
         1716 &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
         1717 &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
         1718 &lt;code&gt;Makefile&lt;/code&gt; or something similar in your projects: you don't need to remember
         1719 the exactly steps that you need to get things working. If I see one project of
         1720 mine having a &lt;code&gt;Makefile&lt;/code&gt;, I can be reasonably confident that I can get it
         1721 working by just running &lt;code&gt;make&lt;/code&gt;.&lt;/p&gt;
         1722 &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
         1723 not related to the build process but are there to help me manage my blog posts.
         1724 Remember the rules I explained above? Maybe not, but it should be no problem,
         1725 because:&lt;/p&gt;
         1726 &lt;pre&gt;&lt;code&gt;$ make post TITLE=&amp;quot;My new blog post&amp;quot;
         1727 mkdir -p &amp;quot;2024-08-13&amp;quot;
         1728 ./.scripts/gen-post.sh &amp;quot;2024-08-13&amp;quot; &amp;quot;My new blog post&amp;quot;
         1729 Creating file: 2024-08-13/02-my-new-blog-post.md
         1730 &lt;p&gt;$ cat 2024-08-13/02-my-new-blog-post.md&lt;/p&gt;
         1731 &lt;h1&gt;My new blog post&lt;/h1&gt;
         1732 &lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
         1733 &lt;p&gt;This command, &lt;code&gt;make post&lt;/code&gt;, is responsible for:&lt;/p&gt;
         1734 &lt;ol&gt;
         1735 &lt;li&gt;Create a new directory for today, if it doesn't exist&lt;/li&gt;
         1736 &lt;li&gt;Run the
         1737 &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;
         1738 script, that:
         1739 &lt;ol&gt;
         1740 &lt;li&gt;Enumerates all posts from the day, so we can number the new post correctly
         1741 &lt;ul&gt;
         1742 &lt;li&gt;We already had this post planned for 2024-08-13, so the new post is 02&lt;/li&gt;
         1743 &lt;/ul&gt;
         1744 &lt;/li&gt;
         1745 &lt;li&gt;Slugify the title, so we can create each Markdown file with the correct
         1746 filename&lt;/li&gt;
         1747 &lt;li&gt;Creates a new Markdown file with the title as a &lt;code&gt;h1&lt;/code&gt; header&lt;/li&gt;
         1748 &lt;/ol&gt;
         1749 &lt;/li&gt;
         1750 &lt;/ol&gt;
         1751 &lt;p&gt;The steps above may or may not seen trivial, and for a while I was doing them
         1752 manually. But not having to think what is the current date or if I already
         1753 posted that day or what is the slug is for the title make (pun intended) my
         1754 like much easier.&lt;/p&gt;
         1755 &lt;p&gt;Yes, the code is ugly. The way variables works in &lt;code&gt;Make&lt;/code&gt; is that you can
         1756 declare then inside the &lt;code&gt;Makefile&lt;/code&gt;, but they can be overwritten in the terminal
         1757 if you pass them. I used this to allow &lt;code&gt;make post&lt;/code&gt; to also work for future
         1758 posts:&lt;/p&gt;
         1759 &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
         1760 &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;
         1761 &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;
         1762 &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
         1763 &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
         1764 instead of default (that would be the current day), and &lt;code&gt;_PARSED_DAY&lt;/code&gt; is the
         1765 day we use to actually create the directory. We can actually pass any date
         1766 format recognised by
         1767 &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;,
         1768 not just &lt;code&gt;YYYY-MM-DD&lt;/code&gt;.&lt;/p&gt;
         1769 &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
         1770 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;
         1771 &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
         1772 &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;
         1773 &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;
         1774 &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
         1775 &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;
         1776 &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
         1777 ignored&lt;/a&gt;
         1778 during my directory parser to mean they're a draft post and not ready to be
         1779 published. And the reason I created those targets is because I was tired of
         1780 trying to hide or unhide a file manually.&lt;/p&gt;
         1781 &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
         1782 everything&lt;/a&gt;,
         1783 you also need to have some way to automate your tasks. &lt;code&gt;Makefile&lt;/code&gt; is one way to
         1784 do it, maybe not the best way to do it, but it works and it is available
         1785 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
         1786 post&lt;/a&gt; talking why
         1787 my favorite device is a Chromebook. In this post I want to talk about what
         1788 makes me this device unhappy, and comment about things that if changed would
         1789 make it a much better device.&lt;/p&gt;
         1790 &lt;p&gt;But before talking about the negative aspects, let me talk about a positive
         1791 aspect that I just briefly talked in the previous post: the screen. It is a
         1792 HiDPI screen (2000x1200 resolution in 10.95''), that is unexpected bright (400
         1793 nits according to the
         1794 &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;).
         1795 It is difficult to find laptops at the same price with a screen that good. At
         1796 10.95'' in its default resolution I find it too small (like 1250x750), but I
         1797 find the font size acceptable at 115% scale (1087x652). Yes, it result in a
         1798 small workspace, but this is not a big issue for what I do in this device. It
         1799 is also only 60Hz, but I thought I would miss high refresh rate more than I
         1800 actually miss in this device.&lt;/p&gt;
         1801 &lt;p&gt;Update: I forgot to say one thing about the screen: it scratches really easy. I
         1802 got my screen scratched after the first day of usage, and considering the price
         1803 I don't think the screen has a hardened glass. I bought a cheap glass screen
         1804 protector and this did the trick though, even hiding the previous scratch, and
         1805 I have zero issues with the screen now.&lt;/p&gt;
         1806 &lt;p&gt;Now the first aspect that I don't like: the speakers. They sound tiny and even
         1807 at maximum volume it is not really loud. The speakers is the only reason why I
         1808 still keep my &lt;a href="https://www.gsmarena.com/xiaomi_pad_5-11042.php"&gt;Xiaomi Pad 5&lt;/a&gt;,
         1809 because I like to watch animes/videos before sleep and having good speakers is
         1810 a must.&lt;/p&gt;
         1811 &lt;p&gt;The keyboard has that issue that I mentioned in the previous post: sometimes
         1812 the key get stuck, and I get duplicated characters. But it also has some minor
         1813 issues that I didn't talked about: the first one is the UK layout that has some
         1814 extra keys that I have no use for, but this also makes the keys that I use
         1815 smaller. Very much a &amp;quot;me&amp;quot; problem, since if I had got a US version I wouldn't
         1816 have those issues, but an issue nonetheless that gets worse considering how
         1817 small the keyboard is. I am actually suprised how fast I can type considering
         1818 how many issues this keyboard has, so maybe this is a testament that this
         1819 keyboard is not actually that bad.&lt;/p&gt;
         1820 &lt;p&gt;The other keyboard issue is a problem that affects all Chromebooks: its custom
         1821 layout. Google replaced a few keys like Fn keys with shortcuts and replaced the
         1822 Caps Lock with a
         1823 &lt;a href="https://chromeunboxed.com/chromebook-launcher-now-everything-button"&gt;&amp;quot;Everything&amp;quot;&lt;/a&gt;
         1824 key (that is similar to the Windows Key), while removing Windows Key from its
         1825 place. I actually have less issue with this than I initially though: I don't
         1826 care too much about Fn keys (except when using IntelliJ, but that is something
         1827 that I only use at &lt;code&gt;$CURRENT_JOB&lt;/code&gt;), and ChromeOS is surprisingly powerful in
         1828 its customisation, allowing you to swap key functionality. I remap Everything
         1829 key with Esc, and Esc for the Everything key, and I can get productive in my
         1830 &lt;code&gt;neovim&lt;/code&gt; setup.&lt;/p&gt;
         1831 &lt;p&gt;And finally, let me talk more about the performance: yes, it is bad, but
         1832 bearable once you get used to. The issue is both the CPU and IO. While the CPU,
         1833 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
         1834 2&lt;/a&gt;
         1835 is octa-core, it has only 2 high performance CPU cores vs 6 low performance
         1836 ones (2xARM Cortex A76 vs 6xARM Cortex A55). If it was something like 4x4, it
         1837 would be much better. The fact that the cores are old doesn't help either.&lt;/p&gt;
         1838 &lt;p&gt;But the worst part is the IO. Not only it uses a eMMC module, it is slow:&lt;/p&gt;
         1839 &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;
         1840 &lt;p&gt;I don't know how much more expensive it would be to put a
         1841 &lt;a href="https://en.wikipedia.org/wiki/Universal_Flash_Storage"&gt;UFS&lt;/a&gt; instead of eMMC in
         1842 this device, but this is probably the choice that would most increase
         1843 performance in this device, especially considering how aggressive Chromebooks
         1844 use (z)swap.&lt;/p&gt;
         1845 &lt;p&gt;Update 2: I forgot to talk about the fact that the exterior of the device is
         1846 covered in cloth. I thought I would hate this at first, but nowadays I kind
         1847 like it. And it is also nice that it will never get scratched, I don't care too
         1848 much about the exterior of this device and it is the only device that I have at
         1849 home that doesn't have additional protection (except the screen protector
         1850 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
         1851 if I could have the surgery I discovered that I was suffering from dry eyes.
         1852 Thanks to this, my ophthalmologist recommended that every 20 minutes while
         1853 using screens, I look somewhere 20 feet away for 20 seconds, a technique known
         1854 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;
         1855 &lt;p&gt;I had issues following this rule because I never remembered to do the pauses. I
         1856 initially tried to setup an alarm, but this became annoying, so I decided to
         1857 try to find a program. I found
         1858 &lt;a href="https://tonyh4156.github.io/20-20-20.github.io/"&gt;this&lt;/a&gt; one for macOS that
         1859 worked fine, but it bothered me that it was compiled for x86_64 while I was
         1860 using a M1 MacBook Pro at the time, and also I needed something that worked in
         1861 Linux.&lt;/p&gt;
         1862 &lt;p&gt;Since I never found a good multi-platform alternative, I decided to write my
         1863 own. This became
         1864 &lt;a href="https://github.com/thiagokokada/twenty-twenty-twenty/"&gt;twenty-twenty-twenty&lt;/a&gt;,
         1865 the first &lt;a href="https://kokada.dev/blog/go-a-reasonable-good-language"&gt;Go&lt;/a&gt; program
         1866 that I ever wrote. I wrote it in Go because I wanted to learn the language, but
         1867 also because Go made it easy to build static binaries. And the first version I
         1868 could build static binaries without issues because I was using
         1869 &lt;a href="https://github.com/gen2brain/beeep"&gt;beeep&lt;/a&gt;, that uses pure Go code in all
         1870 supported platforms. However, it also meant that the notifications in macOS
         1871 looked ugly, since it used
         1872 &lt;a href="https://github.com/gen2brain/beeep/blob/master/beep_darwin.go#L20"&gt;osascript&lt;/a&gt;.&lt;/p&gt;
         1873 &lt;p&gt;I wanted better integration with macOS, so this meant switching libraries.
         1874 After searching for a while, the
         1875 &lt;a href="https://pkg.go.dev/gioui.org/x/notify"&gt;notify&lt;/a&gt; library from
         1876 &lt;a href="https://gioui.org/"&gt;GioUI&lt;/a&gt; is the one that seemed to work better. It
         1877 implements notification in macOS using its native framework, so it works much
         1878 better, but sadly it meant losing static binaries because it depends in CGO.&lt;/p&gt;
         1879 &lt;p&gt;Not a big loss initially, because I am only depending in Foundation inside
         1880 macOS (that should always be available), and in Linux I could still statically
         1881 compile. However I eventually added more features like sound
         1882 (via &lt;a href="https://github.com/gopxl/beep"&gt;beep&lt;/a&gt;) and tray icon (via
         1883 &lt;a href="https://github.com/fyne-io/systray"&gt;systray&lt;/a&gt;), that meant I needed CGO in both
         1884 macOS and Linux.&lt;/p&gt;
         1885 &lt;p&gt;Losing static binaries in Linux is a much bigger deal, since Linux is a moving
         1886 target. The general recommendation for building CGO binaries statically is
         1887 using
         1888 &lt;a href="https://eli.thegreenplace.net/2024/building-static-binaries-with-go-on-linux/"&gt;musl&lt;/a&gt;,
         1889 but this also means building all dependencies that we need using musl (e.g.:
         1890 &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
         1891 [beep/oto]). This generally means pain, but Nix makes it easy.&lt;/p&gt;
         1892 &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
         1893 file&lt;/a&gt;
         1894 that builds our Go module (simplified below for brevity):&lt;/p&gt;
         1895 &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;
         1896 &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;
         1897 &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;
         1898 &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;
         1899 &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;
         1900 &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;
         1901 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
         1902 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1903 &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;
         1904 &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;
         1905 &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;
         1906 &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;
         1907 &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;
         1908 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1909 &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;
         1910 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1911 &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;
         1912 &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;
         1913 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
         1914 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1915 &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;
         1916 &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;
         1917 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;];&lt;/span&gt;
         1918 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1919 &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;
         1920 &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;
         1921 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1922 &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;
         1923 &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;
         1924 &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;
         1925 &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;
         1926 &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;
         1927 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
         1928 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1929 &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;
         1930 &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;
         1931 &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;
         1932 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1933 &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;
         1934 &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;
         1935 &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;
         1936 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
         1937 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1938 &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;
         1939 &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;
         1940 &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;
         1941 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1942 &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;
         1943 &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;
         1944 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         1945 &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;
         1946 &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;
         1947 &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;
         1948 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
         1949 &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;
         1950 &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;
         1951 &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;
         1952 &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;
         1953 &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;
         1954 &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;
         1955 &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;
         1956 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
         1957 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;});&lt;/span&gt;
         1958 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         1959 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         1960 &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
         1961 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
         1962 automatically builds all packages statically with &lt;code&gt;musl&lt;/code&gt;. Also we need pass
         1963 some &lt;a href="https://honnef.co/articles/statically-compiled-go-programs-always-even-with-cgo-using-musl/"&gt;extra
         1964 flags&lt;/a&gt;
         1965 to the Go compiler (i.e.: &lt;code&gt;-linkmode external -extldflags &amp;quot;-static&amp;quot;&lt;/code&gt;), but this
         1966 is a requirement from Go.&lt;/p&gt;
         1967 &lt;p&gt;So, does it work? Let's test:&lt;/p&gt;
         1968 &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
         1969 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
         1970 &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
         1971 &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
         1972 &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;
         1973 &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
         1974 &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
         1975 &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
         1976 &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
         1977 &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
         1978 &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
         1979 &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
         1980 &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
         1981 &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
         1982 related because of my usage of PipeWire and the binary may work in a pure ALSA
         1983 system, but I don't have access to one. Maybe adding &lt;code&gt;pipewire&lt;/code&gt; to
         1984 &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
         1985 statically (because of its dependencies). I think this is a good show how easy
         1986 it is to statically compilation is in Nix, but also how complex static binaries
         1987 are to get correctly.&lt;/p&gt;
         1988 &lt;p&gt;Bonus points for
         1989 &lt;a href="https://nix.dev/tutorials/cross-compilation.html"&gt;cross-compilation&lt;/a&gt;. We can
         1990 easily cross-compile by using &lt;code&gt;pkgsCross&lt;/code&gt;:&lt;/p&gt;
         1991 &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;
         1992 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;
         1993 &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;
         1994 &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;
         1995 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="c1"&gt;# ...&lt;/span&gt;
         1996 &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;
         1997 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
         1998 &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;
         1999 &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;
         2000 &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;
         2001 &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;
         2002 &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;
         2003 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
         2004 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;});&lt;/span&gt;
         2005 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         2006 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2007 &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.:
         2008 &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
         2009 &lt;code&gt;pkgsStatic&lt;/code&gt; to statically cross compile binaries:&lt;/p&gt;
         2010 &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
         2011 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
         2012 &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
         2013 &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
         2014 &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
         2015 bits&lt;/a&gt; post in this
         2016 blog, I gave that title prefix without much thought: I knew it was supposed to
         2017 be a quicker post than my previous one, but I never thought too deeply about
         2018 it. But after:&lt;/p&gt;
         2019 &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
         2020 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;4
         2021 &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
         2022 quick bits really quick, or at least quicker? Let's see:&lt;/p&gt;
         2023 &lt;pre&gt;&lt;code&gt;$ wc -w **/*.md
         2024  1107 2024-07-26/01-writing-nixos-tests-for-fun-and-profit.md
         2025  1220 2024-07-26/02-using-github-as-a-bad-blog-platform.md
         2026   286 2024-07-27/01-quick-bits-nix-shell-is-cursed.md
         2027   387 2024-07-29/01-quick-bits-why-you-should-automate-everything.md
         2028  1060 2024-07-29/02-go-a-reasonable-good-language.md
         2029  1380 2024-07-30/01-first-impressions-fpgbc.md
         2030  1238 2024-07-31/01-generating-yaml-files-with-nix.md
         2031  2308 2024-08-01/01-troubleshooting-zsh-lag-and-solutions-with-nix.md
         2032   504 2024-08-01/02-quick-bits-realise-nix-symlinks.md
         2033   834 2024-08-04/01-make-nixd-modules-completion-work-anywhere-with-flakes.md
         2034  1147 2024-08-05/01-my-favorite-device-is-a-chromebook.md
         2035   394 2024-08-07/01-quick-bits-is-crostini-a-microvm.md
         2036   120 README.md
         2037 11985 total
         2038 &lt;/code&gt;&lt;/pre&gt;
         2039 &lt;p&gt;While using &lt;code&gt;wc&lt;/code&gt; is probably not the best way to measure word count (especially
         2040 in this blog, since I tend to write lots of code snippets), I think this at
         2041 least it gives me a good insight: yes, quick bits are quicker, and they're
         2042 basically posts with a soft limit around 500 words. So expect in future this
         2043 limit to be used.&lt;/p&gt;
         2044 &lt;p&gt;By the way, at:&lt;/p&gt;
         2045 &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
         2046 &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
         2047 &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
         2048 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
         2049 conclusion I got after some searching.&lt;/p&gt;
         2050 &lt;p&gt;Someone asked me in my &lt;a href="https://kokada.dev/blog/my-favorite-device-is-a-chromebook"&gt;previous
         2051 post&lt;/a&gt; about my
         2052 Chromebook if Crostini could be considered a micro VM. This was a interesting
         2053 question, so I decided to do another post.&lt;/p&gt;
         2054 &lt;p&gt;To start, it is really difficult to get a good definition of what a &amp;quot;micro VM&amp;quot;
         2055 is. &lt;a href="https://firecracker-microvm.github.io/"&gt;Firecracker&lt;/a&gt; defines itself as a
         2056 micro VM, and describes itself in its website as:&lt;/p&gt;
         2057 &lt;blockquote&gt;
         2058 &lt;p&gt;Firecracker is a virtual machine monitor (VMM) that uses the Linux
         2059 Kernel-based Virtual Machine (KVM) to create and manage microVMs. Firecracker
         2060 has a minimalist design. It excludes unnecessary devices and guest
         2061 functionality to reduce the memory footprint and attack surface area of each
         2062 microVM. This improves security, decreases the startup time, and increases
         2063 hardware utilization.&lt;/p&gt;
         2064 &lt;/blockquote&gt;
         2065 &lt;p&gt;Now looking at Crostini, its heart is a VMM called
         2066 &lt;a href="https://crosvm.dev/"&gt;crosvm&lt;/a&gt;. It is described in its
         2067 &lt;a href="https://chromium.googlesource.com/chromiumos/platform/crosvm/+/HEAD/README.md"&gt;README&lt;/a&gt;
         2068 as:&lt;/p&gt;
         2069 &lt;blockquote&gt;
         2070 &lt;p&gt;crosvm is a virtual machine monitor (VMM) based on Linux’s KVM hypervisor,
         2071 with a focus on simplicity, security, and speed. crosvm is intended to run
         2072 Linux guests, originally as a security boundary for running native
         2073 applications on the ChromeOS platform. Compared to QEMU, crosvm doesn’t
         2074 emulate architectures or real hardware, instead concentrating on
         2075 paravirtualized devices, such as the virtio standard.&lt;/p&gt;
         2076 &lt;/blockquote&gt;
         2077 &lt;p&gt;Similar descriptions right? Actually Firecracker website says it &amp;quot;started from
         2078 Chromium OS's Virtual Machine Monitor, crosvm, an open source VMM written in
         2079 Rust&amp;quot;. So I would say it is safe to say crosvm itself is a micro VM.&lt;/p&gt;
         2080 &lt;p&gt;But
         2081 &lt;a href="https://www.chromium.org/chromium-os/developer-library/guides/containers/containers-and-vms/"&gt;Crostini&lt;/a&gt;
         2082 itself is a combination of virtualization AND containerization. Basically
         2083 inside the VM it runs a Linux kernel and &lt;a href="https://linuxcontainers.org/"&gt;LXC&lt;/a&gt;,
         2084 that can start arbitrary containers inside it. From the Crostini documentation
         2085 this choice seems to be to keep startup times down, and also to increase
         2086 security (e.g.: in case of a security issue inside the container).&lt;/p&gt;
         2087 &lt;p&gt;This is definitely an interesting choice, since containers allow the overhead
         2088 of each distro that you run inside Crostini to be low, and the main VM itself
         2089 (called
         2090 &lt;a href="https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/HEAD/project-termina/"&gt;Termina&lt;/a&gt;)
         2091 should have low overhead too thanks to crosvm.&lt;/p&gt;
         2092 &lt;p&gt;By the way, if you want to learn more how &amp;quot;devices&amp;quot; works inside a micro VM
         2093 like crosvm, I recommend &lt;a href="https://prilik.com/blog/post/crosvm-paravirt/"&gt;this blog
         2094 post&lt;/a&gt; talking about
         2095 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
         2096 &amp;quot;personal computing&amp;quot; that I do nowadays is mostly done in one of the most
         2097 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
         2098 3&lt;/a&gt;,
         2099 that I bought for around EUR300. I was thinking why, because it is woefully
         2100 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
         2101 2&lt;/a&gt;,
         2102 a CPU that was already considered slow 3 years ago, coupled with an eMMC for
         2103 storage, that is not much faster than a HDD. At least I have the 8GB RAM
         2104 version instead of the 4GB one.&lt;/p&gt;
         2105 &lt;p&gt;It is a hybrid device, one that can be used as either a tablet or laptop, but
         2106 it is compromised experience in both cases: as a tablet, it lacks the better
         2107 touch optimised interface from iOS or Android; as a laptop, you have to depend
         2108 on the stand to adjust the screen, and the detachable keyboard is worse than
         2109 any laptop I have ever owned: getting keys stucked and characters being
         2110 duplicated as a result is a common occurence. It is not so bad that I can't get
         2111 things done though. About the trackpad: its biggest quality is that I never
         2112 feel the need to use the touchscreen in laptop mode, that is to say that it is
         2113 acceptable. Just crank up the pointer speed in ChromeOS settings, otherwise you
         2114 never get anywhere since the trackpad is so small. There is also an active
         2115 stylus, that helped me sometimes when I needed to sign something but otherwise
         2116 I can't comment too much.&lt;/p&gt;
         2117 &lt;p&gt;But I really love this device. It is generally the only device that I bring in
         2118 trips nowadays, because while it is compromised it works well enough: I can use
         2119 to consume media in tablet mode (the fact that ChromeOS supports Android apps
         2120 is a plus in those cases), browse the web and even do Linux stuff (more about
         2121 this later). The fact that it is small (the size remembers me of a
         2122 &lt;a href="https://en.wikipedia.org/wiki/Netbook"&gt;netbook&lt;/a&gt;), lightweight (~1KG, including
         2123 the keyboard), has a good screen (that is bright and HiDPI) and good battery
         2124 life (I don't have numbers but I almost never worry about it) is what makes
         2125 this device the perfect companion to trips.&lt;/p&gt;
         2126 &lt;p&gt;Also, it has 2 USB-C ports and supports DisplayPort alt-mode, so it means you
         2127 can charge it, connect to a external display and peripherals, all at the same
         2128 time. Sadly, the maximum output resolution I got was 1080p (2560x1080),
         2129 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
         2130 success&lt;/a&gt;
         2131 at 1440p, and the specs suggests it supports 4k. It may be my Dell S3423DWC
         2132 monitor being wonky, the fact that it is Ultrawide or the cable, who knows? I
         2133 even tried to change the monitor to &amp;quot;High Resolution&amp;quot; mode in settings, but to
         2134 no avail.&lt;/p&gt;
         2135 &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
         2136 brief&lt;/a&gt;
         2137 for Snapdragon 7c Gen 2, it seems it supports up to QHD@60Hz (1440p) for the
         2138 external display, and not 4k. This explains why it doesn't work at maximum
         2139 resolution in my Dell S3423DWC, since while it is 1440p it is Ultrawide, so the
         2140 resolution is bigger than QHD (3440x1440 vs 2560x1440).&lt;/p&gt;
         2141 &lt;p&gt;ChromeOS is also really interesting nowadays. To start, it is designed from the
         2142 ground up to be a &lt;a href="https://support.google.com/chromebook/answer/3438631"&gt;secure computing
         2143 environment&lt;/a&gt;, probably
         2144 the most secure OS for consumers right now. Being a Chrome-first OS makes it a
         2145 compromised experience, for example, it is the only device that I use Chrome as
         2146 my main browser (since I personally prefer Firefox). But having a OS that boots
         2147 fast is great: I never worry about OS updates because I know the device will be
         2148 ready in seconds after a reboot. And the whole desktop experience inside the
         2149 ChromeOS desktop is good, having shortcuts for many operations so you can get
         2150 things done fast, and support for virtual desktops (ChromeOS call it &amp;quot;desks&amp;quot;)
         2151 means you can organise your windows as much as you want.&lt;/p&gt;
         2152 &lt;p&gt;And what I think makes ChromeOS really powerful is
         2153 &lt;a href="https://chromeos.dev/en/linux"&gt;Crostini&lt;/a&gt;, a full Linux VM that you can run
         2154 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
         2155 distros&lt;/a&gt;
         2156 though) with a deep integration with ChromeOS, so you can run even graphical
         2157 programs without issues (including OpenGL!):&lt;/p&gt;
         2158 &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;
         2159 &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;
         2160 &lt;p&gt;This is all thanks to
         2161 &lt;a href="https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/vm_tools/sommelier/README.md"&gt;Sommelier&lt;/a&gt;,
         2162 a nested Wayland compositor that runs inside Crostini and allow both Wayland
         2163 and X11 applications to be forwarded to ChromeOS. The integration is so good
         2164 that I can run Firefox inside Crostini and works well enough, but sadly Firefox
         2165 is too slow in this device (I am not sure if the issue is ChromeOS or Firefox,
         2166 but I suspect the later since Google does some optimisation per device).&lt;/p&gt;
         2167 &lt;p&gt;One interesting tidbit about the OpenGL situation in this device: this seems to
         2168 be the first Chromebook to ship with open source drivers, thanks to Freedreno.
         2169 There is &lt;a href="https://www.youtube.com/watch?v=8mnjSmN03VM"&gt;this&lt;/a&gt; very interesting
         2170 presentation done by Rob Clark in XDC 2021, that I recommended anyone
         2171 interested in free drivers to watch (the reference design of Duet 3 is
         2172 &lt;a href="https://chromeunboxed.com/chromebook-tablet-snapdragon-7c-homestar-coachz-strongbad"&gt;Strongbad&lt;/a&gt;).&lt;/p&gt;
         2173 &lt;p&gt;The Crostini integration is probably the best VM integration with Linux I ever
         2174 saw in an OS: you can manage files inside the VM, share directories between the
         2175 OS and VM, copy and paste works between the two, GUI applications installed
         2176 inside the VM appear in the ChromeOS menu, memory allocation inside the VM is
         2177 transparent, etc. Even the themes for Linux GUI applications are customised to
         2178 match ChromeOS. It is unironically one of the best Linux desktop experiences I
         2179 ever had.&lt;/p&gt;
         2180 &lt;p&gt;Of course I am using Nix, but since the Crostini integration depends on some
         2181 services being configured and installed, I decided to run Nix inside Debian
         2182 instead of NixOS and run &lt;a href="https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone"&gt;Home-Manager
         2183 standalone&lt;/a&gt;.
         2184 I recommend checking the official &lt;a href="https://wiki.nixos.org/wiki/Installing_Nix_on_Crostini"&gt;NixOS Wiki article about
         2185 Crostini&lt;/a&gt;, that details
         2186 how to register applications in ChromeOS (so desktop applications appear in
         2187 menu) and use &lt;a href="https://github.com/nix-community/nixGL"&gt;nixGL&lt;/a&gt; to make OpenGL
         2188 applications work.&lt;/p&gt;
         2189 &lt;p&gt;Like I said at the start of the article, the device is woefully slow thanks to
         2190 its CPU and eMMC. It does mean that, for example, activating my Home-Manager
         2191 configuration takes a while (around 1 minute, vs a few seconds in my laptop).
         2192 But it is much faster than say,
         2193 &lt;a href="https://github.com/nix-community/nix-on-droid-app"&gt;nix-on-droid&lt;/a&gt;, that the
         2194 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
         2195 5&lt;/a&gt;), took 30 minutes until I
         2196 just decided to cancel the operation. Having a proper VM instead of
         2197 &lt;a href="https://wiki.termux.com/wiki/PRoot"&gt;proot&lt;/a&gt; makes all the difference here.&lt;/p&gt;
         2198 &lt;p&gt;I can even do some light programming here: using my
         2199 &lt;a href="https://kokada.dev/blog/troubleshoting-zsh-lag-and-solutions-with-nix"&gt;ZSH&lt;/a&gt;
         2200 and neovim configuration (including LSP for coding) is reasonable fast. For
         2201 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
         2202 blog&lt;/a&gt;
         2203 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
         2204 Android&lt;/a&gt; to connect to any
         2205 other of my hosts via SSH. Yes, the Tailscale app works in Crostini, sadly
         2206 without MagicDNS, so you need to use the internal Tailscale IPs instead.&lt;/p&gt;
         2207 &lt;p&gt;Until Google decides to give us a proper VM or user namespaces in Android or
         2208 release a hybrid Chromebook device with better specs, this small Chromebook
         2209 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
         2210 result&amp;quot;).&lt;/p&gt;
         2211 &lt;p&gt;I recently switched from &lt;a href="https://github.com/oxalica/nil"&gt;nil&lt;/a&gt; to
         2212 &lt;a href="https://github.com/nix-community/nixd"&gt;nixd&lt;/a&gt; as my LSP of choice for Nix. I
         2213 was curious in &lt;code&gt;nixd&lt;/code&gt; for a long time since the fact that it can eval Nix code
         2214 means it can offer much more powerful completion than the &lt;code&gt;nil&lt;/code&gt;'s static
         2215 analysis, however it used to be difficult to setup. Nowadays it is much easier,
         2216 basically doing the right thing as long as you have &lt;code&gt;NIX_PATH&lt;/code&gt; setup, and you
         2217 get both package and NixOS modules completion.&lt;/p&gt;
         2218 &lt;p&gt;Getting Home-Manager modules though needs some setup. The recommended way to
         2219 setup accordingly to the &lt;a href="https://github.com/nix-community/nixd/blob/fe202307eaf7e89c4366ed927af761482a6065c8/nixd/docs/configuration.md"&gt;official
         2220 documentation&lt;/a&gt;
         2221 is to use the following for Flake based configurations (using neovim
         2222 configuration here, but it should be easy to adapt to other editors):&lt;/p&gt;
         2223 &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;
         2224 &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;
         2225 &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;
         2226 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
         2227 &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;
         2228 &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;
         2229 &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;
         2230 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
         2231 &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;
         2232 &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;
         2233 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
         2234 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
         2235 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;-- ...&lt;/span&gt;
         2236 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2237 &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
         2238 will only work if you open your editor in your &lt;a href="https://github.com/thiagokokada/nix-configs"&gt;Nix configuration
         2239 repository&lt;/a&gt;. For any other
         2240 repository, &lt;code&gt;nixosConfigurations.miku-nixos&lt;/code&gt; or &lt;code&gt;homeConfigurations.home-linux&lt;/code&gt;
         2241 will not exist and the completion will not work.&lt;/p&gt;
         2242 &lt;p&gt;It may look like this is easy to fix if you have
         2243 &lt;a href="https://nixos.org/manual/nixos/unstable/options#opt-_module.args"&gt;&lt;code&gt;specialArgs&lt;/code&gt;&lt;/a&gt;
         2244 (or &lt;code&gt;extraSpecialArgs&lt;/code&gt; in Home-Manager) set to have your
         2245 Flakes inputs, but:&lt;/p&gt;
         2246 &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;
         2247 &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;
         2248 &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;
         2249 &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;
         2250 &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;
         2251 &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;
         2252 &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;
         2253 &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;
         2254 &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;
         2255 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2256 &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;
         2257 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2258 &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;
         2259 &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;
         2260 &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;
         2261 &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;
         2262 &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;
         2263 &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;
         2264 &lt;/span&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;
         2265 &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
         2266 impure, since there is no &lt;code&gt;flake.lock&lt;/code&gt;).&lt;/p&gt;
         2267 &lt;p&gt;The default configuration for &lt;code&gt;nixd&lt;/code&gt; makes NixOS completion work even outside
         2268 of my configuration repo, and it is fast. How? I decided to take a look at the
         2269 &lt;code&gt;nixd&lt;/code&gt; source code and found
         2270 &lt;a href="https://github.com/nix-community/nixd/blob/d938026c55c7c36a6e79afd9627459160b4924ed/nixd/lib/Controller/LifeTime.cpp#L33C11-L35C76"&gt;this&lt;/a&gt;
         2271 (formatted here for legibility):&lt;/p&gt;
         2272 &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;
         2273 &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;
         2274 &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;
         2275 &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;
         2276 &lt;/span&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;
         2277 &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;
         2278 &lt;/span&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;
         2279 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;];&lt;/span&gt;
         2280 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
         2281 &lt;/span&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;
         2282 &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
         2283 said above, it depends in &lt;code&gt;NIX_PATH&lt;/code&gt; being correctly set. Can we fix this to
         2284 use our Flake inputs instead? After some tries in the Nix REPL, I got the
         2285 following:&lt;/p&gt;
         2286 &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;
         2287 &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;
         2288 &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;
         2289 &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;
         2290 &lt;/span&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;
         2291 &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;
         2292 &lt;/span&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;
         2293 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;];&lt;/span&gt;
         2294 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
         2295 &lt;/span&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;
         2296 &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;
         2297 &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;
         2298 &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;
         2299 &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;
         2300 &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;
         2301 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
         2302 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
         2303 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2304 &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
         2305 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
         2306 code&lt;/a&gt;
         2307 to find the answer:&lt;/p&gt;
         2308 &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;
         2309 &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;
         2310 &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;
         2311 &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;
         2312 &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;
         2313 &lt;/span&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;
         2314 &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;
         2315 &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;
         2316 &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;
         2317 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         2318 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
         2319 &lt;/span&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;
         2320 &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
         2321 system (including &lt;code&gt;evalModules&lt;/code&gt;). This includes e.g.: extra types used in
         2322 Home-Manager only. Also, we need to disable &lt;code&gt;checks&lt;/code&gt;, otherwise we will hit
         2323 some validations (e.g.: missing &lt;code&gt;stateVersion&lt;/code&gt;). I am not sure if this causes
         2324 any issue for module completion yet, I may set it in the future.&lt;/p&gt;
         2325 &lt;p&gt;And for the final result:&lt;/p&gt;
         2326 &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;
         2327 &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;
         2328 &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;
         2329 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
         2330 &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;
         2331 &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;
         2332 &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;
         2333 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
         2334 &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;
         2335 &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;
         2336 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
         2337 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
         2338 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2339 &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
         2340 repository, as long as you're using Flakes. And it is fast, since it doesn't
         2341 need any network access. Since we are already here, let's define &lt;code&gt;nixpkgs&lt;/code&gt; to
         2342 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
         2343 Tavis&lt;/a&gt;, I decided to document my own
         2344 recent journey of reducing terminal (ZSH) lag startup. This post is way less
         2345 interesting than the one from Tavis that uses a debugger to patch applications
         2346 on the fly, but should still be interesting for some. And it also shows how
         2347 powerful Nix can be for some things.&lt;/p&gt;
         2348 &lt;p&gt;For context, I have basically 3 systems where I interact with terminal
         2349 frequently:&lt;/p&gt;
         2350 &lt;ul&gt;
         2351 &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;
         2352 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
         2353 4750U&lt;/a&gt;)
         2354 and disk (NVMe)&lt;/li&gt;
         2355 &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
         2356 Pro&lt;/a&gt;
         2357 with a really fast CPU (M1 Pro) and disk, but of course running macOS, being
         2358 phased-out since this is a job owned machine and I am changing jobs right
         2359 now, but should be replaced with another one soon™&lt;/li&gt;
         2360 &lt;li&gt;&lt;a href="https://chromeunboxed.com/lenovo-chromebook-duet-3-review-perfect-sequel"&gt;Chromebook Duet
         2361 3&lt;/a&gt;
         2362 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
         2363 2&lt;/a&gt;)
         2364 and disk (eMMC, really?)&lt;/li&gt;
         2365 &lt;/ul&gt;
         2366 &lt;p&gt;My experience is similar to Tavis, at around 300ms of startup time I don't care
         2367 too much, but around 500ms+ is where I start to notice. I never had any issues
         2368 with startup time in NixOS itself (I had issues with macOS before, but it was
         2369 not actually the fault of macOS), but in the Chromebook it was awful: 600ms+
         2370 with &lt;a href="https://www.instabug.com/blog/understanding-cold-hot-and-warm-app-launch-time"&gt;hot
         2371 start&lt;/a&gt;,
         2372 while cold start it could take multiple seconds.&lt;/p&gt;
         2373 &lt;p&gt;We can check how long ZSH takes to start by using:&lt;/p&gt;
         2374 &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;
         2375 &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
         2376 &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
         2377 use of ZSH. Without this flag ZSH will ignore your &lt;code&gt;~/.zshrc&lt;/code&gt; file, and the
         2378 results will be meaningless.&lt;/p&gt;
         2379 &lt;p&gt;To do a more interesting benchmark, we can use
         2380 &lt;a href="https://github.com/sharkdp/hyperfine"&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
         2381 &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;
         2382 &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
         2383 &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]
         2384 &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
         2385 &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
         2386 shell startup time. A really great tool to have in your toolbox by the way, but
         2387 I digress.&lt;/p&gt;
         2388 &lt;p&gt;So let's do a little time travelling. Going back to commit
         2389 &lt;a href="https://github.com/thiagokokada/nix-configs/tree/b12757f90889653e359a1ab0a8cfd2f90cfabf31"&gt;&lt;code&gt;b12757f&lt;/code&gt;&lt;/a&gt;
         2390 from &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;nix-configs&lt;/a&gt;. Running
         2391 &lt;code&gt;hyperfine&lt;/code&gt; like above from my NixOS laptop, we have:&lt;/p&gt;
         2392 &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;
         2393 &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
         2394 &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]
         2395 &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
         2396 &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;
         2397 &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;
         2398 &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
         2399 &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]
         2400 &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
         2401 &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
         2402 benchmark (so it is a hot start). The cold start times were above 3s. So let's
         2403 investigate what is happening here. We can profile what is taking time during
         2404 the startup of ZSH using &lt;a href="https://www.bigbinary.com/blog/zsh-profiling"&gt;zprof&lt;/a&gt;.
         2405 You can add the following in your &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
         2406 &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;
         2407 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zmodload zsh/zprof
         2408 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2409 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
         2410 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2411 &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;
         2412 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zprof
         2413 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or if using Home-Manager, use the
         2414 &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;
         2415 option. Once we restart ZSH, we will have something like:&lt;/p&gt;
         2416 &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
         2417 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-----------------------------------------------------------------------------------
         2418 &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]
         2419 &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]
         2420 &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
         2421 &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
         2422 &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
         2423 &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
         2424 &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
         2425 &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
         2426 &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
         2427 &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
         2428 &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
         2429 &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]
         2430 &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
         2431 &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]
         2432 &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
         2433 &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
         2434 &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
         2435 &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
         2436 &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
         2437 &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
         2438 &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
         2439 &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; ...
         2440 &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
         2441 &lt;a href="https://github.com/zimfw/zimfw"&gt;zimfw&lt;/a&gt;, the framework that I use to configure
         2442 my ZSH (similar to Oh-My-Zsh). I actually don't use &lt;code&gt;zimfw&lt;/code&gt; directly, instead I
         2443 just load some modules that I find useful, like the &lt;code&gt;zim-completion&lt;/code&gt; and
         2444 &lt;code&gt;zim-ssh&lt;/code&gt; that we can see above. By the way, Zim is generally really well
         2445 optimised for startup time, but those 2 modules are kind slow.&lt;/p&gt;
         2446 &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
         2447 look at it, there isn't much I could do. It seems that the reason
         2448 &lt;code&gt;zim-completion&lt;/code&gt; takes so long during startup is because it is trying to decide
         2449 if it needs to recompile the completions (and replacing it with just a naive
         2450 &lt;code&gt;autoload -U compinit &amp;amp;&amp;amp; compinit&lt;/code&gt; is even worse for startup performance). I
         2451 may eventually replace it for something else, but I really like what Zim brings
         2452 here, so I decided to not touch it for now.&lt;/p&gt;
         2453 &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
         2454 reason I used it is to start a &lt;code&gt;ssh-agent&lt;/code&gt; and keep it between multiple ZSH
         2455 sessions. It shouldn't have that much influence in startup time. So I took a
         2456 look the code (since it is small, I am reproducing it here):&lt;/p&gt;
         2457 &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;
         2458 &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;
         2459 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
         2460 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2461 &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;
         2462 &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;
         2463 &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
         2464 &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;
         2465 &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;
         2466 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2467 &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;
         2468 &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
         2469 &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
         2470 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2471 &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
         2472 &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;
         2473 &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;
         2474 &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;
         2475 &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
         2476 &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;
         2477 &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;
         2478 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2479 &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;
         2480 &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
         2481 &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;
         2482 &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
         2483 &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;
         2484 &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;
         2485 &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
         2486 &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;
         2487 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      ssh-add 2&amp;gt;/dev/null
         2488 &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;
         2489 &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;
         2490 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
         2491 &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
         2492 already running but you open a new shell instance (that doesn't have the
         2493 connection info yet so it will need to load). This will run &lt;code&gt;ssh-add&lt;/code&gt; at 4
         2494 times. How long does &lt;code&gt;ssh-add&lt;/code&gt; takes to run?&lt;/p&gt;
         2495 &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;
         2496 &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
         2497 &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]
         2498 &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
         2499 &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;
         2500 &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.
         2501 &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
         2502 command being tested is too fast.&lt;/p&gt;
         2503 &lt;p&gt;In average we have 4x4ms=16ms of startup time. But keep in mind the worst case
         2504 can be much worse. The question is, how can we improve the situation here?&lt;/p&gt;
         2505 &lt;p&gt;After taking a look, I decided to write my own code, based in some ideas stolen
         2506 from &lt;a href="https://github.com/ohmyzsh/ohmyzsh/blob/67581c53c6458566e174620361e84b364b9034d2/plugins/ssh-agent/ssh-agent.plugin.zsh"&gt;Oh-My-Zsh ssh-agent
         2507 plugin&lt;/a&gt;.
         2508 Here is final version of my
         2509 &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;
         2510 &lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zmodload zsh/net/socket
         2511 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2512 &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;
         2513 &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;
         2514 &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;
         2515 &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;
         2516 &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;
         2517 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
         2518 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2519 &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;
         2520 &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;
         2521 &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;
         2522 &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;
         2523 &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;
         2524 &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;
         2525 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2526 &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;
         2527 &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;
         2528 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2529 &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;
         2530 &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;
         2531 &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
         2532 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2533 &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;
         2534 &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;
         2535 &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;
         2536 &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;
         2537 &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;
         2538 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2539 &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;
         2540 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;(&lt;/span&gt;
         2541 &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;
         2542 &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;
         2543 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;)&lt;/span&gt;
         2544 &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
         2545 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
         2546 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2547 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_start_agent
         2548 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unfunction _check_agent _start_agent
         2549 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea here is simple: using
         2550 &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;
         2551 module from ZSH itself to check if the &lt;code&gt;ssh-agent&lt;/code&gt; is working instead of
         2552 executing &lt;code&gt;ssh-add -l&lt;/code&gt;. The only case we run any program now is to start the
         2553 agent itself if needed. Let's run &lt;code&gt;hyperfine&lt;/code&gt; again:&lt;/p&gt;
         2554 &lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
         2555 Benchmark 1: zsh -ic exit
         2556   Time (mean ± σ):     188.3 ms ±   8.2 ms    [User: 61.1 ms, System: 130.0 ms]
         2557   Range (min … max):   170.9 ms … 198.4 ms    16 runs
         2558 &lt;/code&gt;&lt;/pre&gt;
         2559 &lt;p&gt;Got a good improvement here already. Let's see &lt;code&gt;zprof&lt;/code&gt; again:&lt;/p&gt;
         2560 &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
         2561 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-----------------------------------------------------------------------------------
         2562 &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]
         2563 &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
         2564 &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
         2565 &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
         2566 &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
         2567 &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
         2568 &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
         2569 &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
         2570 &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
         2571 &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
         2572 &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
         2573 &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
         2574 &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]
         2575 &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]
         2576 &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
         2577 &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
         2578 &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
         2579 &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
         2580 &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
         2581 &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
         2582 &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
         2583 &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; ...
         2584 &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
         2585 still the main culprit, but nothing to do for now. Instead of looking at
         2586 &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;
         2587 &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;
         2588 &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;
         2589 &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;
         2590 &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;
         2591 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2592 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
         2593 &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
         2594 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2595 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
         2596 &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;
         2597 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2598 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
         2599 &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;
         2600 &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
         2601 startup considerable. Not necessary for commands fast like &lt;code&gt;fzf&lt;/code&gt; (that is
         2602 written in Go), but let's see
         2603 &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
         2604 in shell script:&lt;/p&gt;
         2605 &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;
         2606 &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
         2607 &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]
         2608 &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
         2609 &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
         2610 in mind that there is a difference between the cold and hot start again. For
         2611 example, &lt;code&gt;fzf&lt;/code&gt;:&lt;/p&gt;
         2612 &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;
         2613 &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
         2614 &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]
         2615 &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
         2616 &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,
         2617 6.8ms can be noticiable, especially if this accumulates with other slow
         2618 starting apps.&lt;/p&gt;
         2619 &lt;p&gt;And the thing is, all those commands are just generating in the end a fixed
         2620 output, at least for the current version of the program. Can we pre-generate
         2621 them instead? If using Nix, of course we can:&lt;/p&gt;
         2622 &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;
         2623 &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;
         2624 &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;
         2625 &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;
         2626 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2627 &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;
         2628 &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;
         2629 &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;
         2630 &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
         2631 &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;
         2632 &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;
         2633 &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
         2634 &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;
         2635 &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;
         2636 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
         2637 &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
         2638 &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
         2639 &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
         2640 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
         2641 &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
         2642 &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;
         2643 &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;
         2644 &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
         2645 &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;
         2646 &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;
         2647 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
         2648 &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
         2649 &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;
         2650 &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;
         2651 &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
         2652 &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;
         2653 &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;
         2654 &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;
         2655 &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
         2656 &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
         2657 already generated in the package. I think this is one of those things that
         2658 really shows the power of Nix: I wouldn't do something similar if I didn't use
         2659 Nix because the risk of breaking something later is big (e.g.: forgetting to
         2660 update the generated files), but Nix makes those things trivial.&lt;/p&gt;
         2661 &lt;p&gt;Let's run &lt;code&gt;hyperfine&lt;/code&gt; again:&lt;/p&gt;
         2662 &lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
         2663 Benchmark 1: zsh -ic exit
         2664   Time (mean ± σ):     162.3 ms ±   4.9 ms    [User: 52.7 ms, System: 111.1 ms]
         2665   Range (min … max):   153.0 ms … 173.4 ms    19 runs
         2666 &lt;/code&gt;&lt;/pre&gt;
         2667 &lt;p&gt;Another good improvement. The last change I did is switching between
         2668 &lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting"&gt;&lt;code&gt;zsh-syntax-highlighting&lt;/code&gt;&lt;/a&gt;
         2669 to
         2670 &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;,
         2671 that is supposed to be faster and have better highlighting too. I got that from
         2672 &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.
         2673 And for the final &lt;code&gt;hyperfine&lt;/code&gt; in my laptop:&lt;/p&gt;
         2674 &lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
         2675 Benchmark 1: zsh -ic exit
         2676   Time (mean ± σ):     138.3 ms ±   7.1 ms    [User: 47.5 ms, System: 91.9 ms]
         2677   Range (min … max):   123.8 ms … 157.9 ms    21 runs
         2678 &lt;/code&gt;&lt;/pre&gt;
         2679 &lt;p&gt;A ~36% improvement, not bad. Let's see how it fares in my Chromebook:&lt;/p&gt;
         2680 &lt;pre&gt;&lt;code&gt;$ hyperfine &amp;quot;zsh -ic exit&amp;quot;
         2681 Benchmark 1: zsh -ic exit
         2682   Time (mean ± σ):     278.2 ms ±  46.9 ms    [User: 88.0 ms, System: 184.8 ms]
         2683   Range (min … max):   204.7 ms … 368.5 ms    11 runs
         2684 &lt;/code&gt;&lt;/pre&gt;
         2685 &lt;p&gt;An even more impressive ~59% improvement. And yes, the shell startup now feels
         2686 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
         2687 &lt;a href="https://github.com/nix-community/home-manager/"&gt;Home-Manager&lt;/a&gt;, there are times
         2688 when you want to test something or maybe debug some issue in your
         2689 configuration. Those times it would be really convenient if you could avoid a
         2690 rebuild of your Home-Manager configuration, since this takes time until
         2691 evaluation and activation.&lt;/p&gt;
         2692 &lt;p&gt;For those times I have this small script in my Nix configuration called
         2693 &lt;code&gt;realise-symlinks&lt;/code&gt;, that is defined as:&lt;/p&gt;
         2694 &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;
         2695 &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;
         2696 &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;
         2697 &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;
         2698 &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;
         2699 &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;
         2700 &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
         2701 &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
         2702 &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
         2703 &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;
         2704 &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;
         2705 &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;
         2706 &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;
         2707 &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;
         2708 &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;
         2709 &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
         2710 &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;
         2711 &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;
         2712 &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
         2713 &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
         2714 &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;
         2715 &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
         2716 &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
         2717 &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
         2718 &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;
         2719 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
         2720 &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;
         2721 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
         2722 &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;
         2723 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2724 &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
         2725 store and it will realise, e.g.: convert to an &amp;quot;actual&amp;quot; file, e.g.:&lt;/p&gt;
         2726 &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
         2727 &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
         2728 &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;
         2729 &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
         2730 &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;
         2731 &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;
         2732 &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--)
         2733 &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;
         2734 &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
         2735 &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
         2736 &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.
         2737 By the way, it also works with directories:&lt;/p&gt;
         2738 &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
         2739 &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
         2740 &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;
         2741 &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
         2742 &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;
         2743 &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;
         2744 &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;
         2745 &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;
         2746 &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--)
         2747 &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--)
         2748 &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--)
         2749 &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--)
         2750 &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;
         2751 &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
         2752 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;total 28K
         2753 &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 .
         2754 &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 ..
         2755 &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
         2756 &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
         2757 &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
         2758 &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
         2759 &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
         2760 can just delete those files and re-run your Home-Manager activation:&lt;/p&gt;
         2761 &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
         2762 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
         2763 &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;
         2764 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
         2765 &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
         2766 &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
         2767 &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;
         2768 &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
         2769 &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:
         2770 &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;
         2771 &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;
         2772 &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--)
         2773 &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
         2774 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
         2775 &lt;a href="https://noyaml.com/"&gt;noyaml.com&lt;/a&gt;. In my personal projects I will never use it,
         2776 preferring either JSON, &lt;a href="https://toml.io/en/"&gt;TOML&lt;/a&gt; or even plain old
         2777 &lt;a href="https://en.wikipedia.org/wiki/INI_file"&gt;INI&lt;/a&gt; files depending on the use case.
         2778 However the ship has sailed already, there are tons of projects everywhere that
         2779 uses YAML: from most CI systems (&lt;a href="https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions"&gt;GitHub
         2780 Actions&lt;/a&gt;,
         2781 &lt;a href="https://circleci.com/docs/introduction-to-yaml-configurations/"&gt;CircleCI&lt;/a&gt;,
         2782 &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;
         2783 &lt;a href="https://man.sr.ht/builds.sr.ht/"&gt;builds.sr.ht&lt;/a&gt;), to
         2784 &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/"&gt;Kubernetes&lt;/a&gt;,
         2785 or in almost every
         2786 &lt;a href="https://guides.rubyonrails.org/configuring.html#configuring-a-database"&gt;Rails&lt;/a&gt;
         2787 application.&lt;/p&gt;
         2788 &lt;p&gt;One way to avoid at least some issues with the language is to write YAML in
         2789 another language. I will show my solution in one of my &lt;a href="https://github.com/thiagokokada/nix-configs/"&gt;personal
         2790 repositories&lt;/a&gt;, writing Nix to
         2791 generate GitHub Actions configuration files. Bonus points for validating the
         2792 result against the schema of GitHub Actions, so the famous &amp;quot;this is supposed to
         2793 be string instead of a list of strings&amp;quot; is gone.&lt;/p&gt;
         2794 &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
         2795 JSON&lt;/a&gt;. What that means is that a JSON file
         2796 &lt;a href="https://yaml.org/spec/1.2-old/spec.html#id2759572"&gt;can be parsed&lt;/a&gt; by a YAML
         2797 parser. And Nix itself generates JSON natively, after all, Nix can be imagined
         2798 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;
         2799 &lt;p&gt;To make things easier, I will assume that you have the &lt;code&gt;nix-commands&lt;/code&gt; and
         2800 &lt;code&gt;flakes&lt;/code&gt; enabled as &lt;code&gt;experimental-features&lt;/code&gt; in your Nix configuration. If not,
         2801 go &lt;a href="https://wiki.nixos.org/wiki/Flakes"&gt;here&lt;/a&gt;.&lt;/p&gt;
         2802 &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;
         2803 &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
         2804 &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;}
         2805 &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
         2806 impractical. We can write the following code inside a &lt;code&gt;foo.nix&lt;/code&gt; file instead:&lt;/p&gt;
         2807 &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;
         2808 &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;
         2809 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2810 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
         2811 &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
         2812 &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;}
         2813 &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
         2814 not the &lt;a href="https://metacpan.org/pod/JSON::XS#JSON-and-YAML"&gt;best idea&lt;/a&gt;. Sadly (or
         2815 maybe not), Nix has no native functionality to export data to YAML. However,
         2816 since we are using Nix, it is trivial to use &lt;code&gt;nixpkgs&lt;/code&gt; to use some program to
         2817 convert from JSON to YAML.&lt;/p&gt;
         2818 &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
         2819 new &lt;code&gt;flake.nix&lt;/code&gt; file and put the following contents:&lt;/p&gt;
         2820 &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;
         2821 &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;
         2822 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2823 &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;
         2824 &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;
         2825 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
         2826 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2827 &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;
         2828 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
         2829 &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;
         2830 &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;
         2831 &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;
         2832 &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;
         2833 &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;
         2834 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
         2835 &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;
         2836 &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;
         2837 &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;
         2838 &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;
         2839 &lt;/span&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;
         2840 &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
         2841 &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
         2842 &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;
         2843 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;};&lt;/span&gt;
         2844 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         2845 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2846 &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
         2847 &lt;code&gt;builtins.toJSON&lt;/code&gt; function, and finally, using &lt;code&gt;pkgs.runCommand&lt;/code&gt; and its
         2848 &lt;code&gt;passAsFile&lt;/code&gt; option to load the contents of the JSON file into
         2849 &lt;a href="https://github.com/sclevine/yj"&gt;yj&lt;/a&gt;, that converts between serialisation
         2850 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
         2851 because it is a single binary Go program, but you can use whatever you prefer.&lt;/p&gt;
         2852 &lt;p&gt;By the way, there is a
         2853 &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;
         2854 inside &lt;code&gt;nixpkgs.lib&lt;/code&gt;, but as of the day of this post it only calls
         2855 &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
         2856 really help here. Another option would be &lt;code&gt;pkgs.formats.yaml.generate&lt;/code&gt;, that
         2857 converts between formats, but it calls
         2858 &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;
         2859 (in Python), so not my favorite choice.&lt;/p&gt;
         2860 &lt;p&gt;If we run the following commands, we can see the result:&lt;/p&gt;
         2861 &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
         2862 &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
         2863 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;foo: bar
         2864 &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
         2865 &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;,
         2866 that builds this blog, to Nix:&lt;/p&gt;
         2867 &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;
         2868 &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;
         2869 &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;
         2870 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         2871 &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;
         2872 &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;
         2873 &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;
         2874 &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;
         2875 &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;
         2876 &lt;/span&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;
         2877 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
         2878 &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;
         2879 &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;
         2880 &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;
         2881 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         2882 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
         2883 &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;
         2884 &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;
         2885 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         2886 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
         2887 &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;
         2888 &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;
         2889 &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;
         2890 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         2891 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
         2892 &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;
         2893 &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;
         2894 &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;
         2895 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         2896 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;];&lt;/span&gt;
         2897 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         2898 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
         2899 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2900 &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
         2901 need to quote it. Not a problem, but something to be aware. And the template
         2902 string in GitHub Actions uses the same &lt;code&gt;${}&lt;/code&gt; that Nix uses, so we need to
         2903 escape.&lt;/p&gt;
         2904 &lt;p&gt;And after running the following commands:&lt;/p&gt;
         2905 &lt;pre&gt;&lt;code&gt;$ nix build .#packages.x86_64-linux.toYAML
         2906 $ cat result/go.yaml
         2907 jobs:
         2908   build:
         2909     permissions:
         2910       contents: write
         2911     runs-on: ubuntu-latest
         2912     steps:
         2913       - uses: actions/checkout@v4
         2914       - name: Set up Go
         2915         uses: actions/checkout@v4
         2916         with:
         2917           go-version: &amp;quot;1.21&amp;quot;
         2918       - name: Update
         2919         run: make
         2920       - env:
         2921           MATAROA_TOKEN: ${{ secrets.MATAROA_TOKEN }}
         2922         name: Publish
         2923         run: make publish
         2924       - name: Commit
         2925         uses: stefanzweifel/git-auto-commit-action@v5
         2926         with:
         2927           commit_message: README/rss:update
         2928 name: Go
         2929 &amp;quot;on&amp;quot;:
         2930   push:
         2931     branches:
         2932       - main
         2933 &lt;/code&gt;&lt;/pre&gt;
         2934 &lt;p&gt;Yes, the keys are not in the same order as we defined, since Nix, like most
         2935 programming languages (with the exception of
         2936 &lt;a href="https://mail.python.org/pipermail/python-dev/2017-December/151283.html"&gt;Python&lt;/a&gt;),
         2937 do not guarantee the insertion order in maps/dicts/attrsets/whatever. But I
         2938 really hope whatever is consuming your YAML is not relying in the order the
         2939 keys are defined (this would be more cursed than YAML already is).&lt;/p&gt;
         2940 &lt;p&gt;So that is basically it. For the bonus points that I talked at the start of the
         2941 post, we can modify &lt;code&gt;pkgs.runCommand&lt;/code&gt; to run some kind of validator. I use
         2942 &lt;a href="https://github.com/mpalmer/action-validator"&gt;&lt;code&gt;action-validator&lt;/code&gt;&lt;/a&gt;, one that I
         2943 particularly packaged in
         2944 &lt;a href="https://github.com/NixOS/nixpkgs/pull/260217"&gt;nixpkgs&lt;/a&gt; to use in those cases.
         2945 But you could use e.g.: a validator of Kubernetes YAML. Or a generic YAML lint
         2946 like this &lt;a href="https://github.com/adrienverge/yamllint"&gt;one&lt;/a&gt;. The possibilities are
         2947 endless.&lt;/p&gt;
         2948 &lt;p&gt;Let's modify our &lt;code&gt;flake.nix&lt;/code&gt; to add the validation:&lt;/p&gt;
         2949 &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;
         2950 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;
         2951 &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;
         2952 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
         2953 &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;
         2954 &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;
         2955 &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;
         2956 &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;
         2957 &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;
         2958 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
         2959 &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;
         2960 &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;
         2961 &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;
         2962 &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;
         2963 &lt;/span&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;
         2964 &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
         2965 &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
         2966 &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
         2967 &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;
         2968 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;};&lt;/span&gt;
         2969 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         2970 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         2971 &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;
         2972 &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
         2973 &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
         2974 &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
         2975 &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
         2976 &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 @@
         2977 &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 = {
         2978 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     build = {
         2979 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       runs-on = &amp;#34;ubuntu-latest&amp;#34;;
         2980 &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;;
         2981 &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; ];
         2982 &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 = [
         2983 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         { uses = &amp;#34;actions/checkout@v4&amp;#34;; }
         2984 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;         {
         2985 &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;
         2986 &lt;pre&gt;&lt;code&gt;$ nix build .#packages.x86_64-linux.toYAML
         2987 error: builder for '/nix/store/j8wr6j1pvyf986sf74hqw8k31lvlzac5-toYAML.drv' failed with exit code 1;
         2988        last 25 log lines:
         2989        &amp;gt;                                 &amp;quot;Additional property 'runs-on' is not allowed&amp;quot;,
         2990        &amp;gt;                             ),
         2991        &amp;gt;                             path: &amp;quot;/jobs/build&amp;quot;,
         2992        &amp;gt;                             title: &amp;quot;Property conditions are not met&amp;quot;,
         2993        &amp;gt;                         },
         2994        &amp;gt;                         Properties {
         2995        &amp;gt;                             code: &amp;quot;properties&amp;quot;,
         2996        &amp;gt;                             detail: Some(
         2997        &amp;gt;                                 &amp;quot;Additional property 'steps' is not allowed&amp;quot;,
         2998        &amp;gt;                             ),
         2999        &amp;gt;                             path: &amp;quot;/jobs/build&amp;quot;,
         3000        &amp;gt;                             title: &amp;quot;Property conditions are not met&amp;quot;,
         3001        &amp;gt;                         },
         3002        &amp;gt;                         Required {
         3003        &amp;gt;                             code: &amp;quot;required&amp;quot;,
         3004        &amp;gt;                             detail: None,
         3005        &amp;gt;                             path: &amp;quot;/jobs/build/uses&amp;quot;,
         3006        &amp;gt;                             title: &amp;quot;This property is required&amp;quot;,
         3007        &amp;gt;                         },
         3008        &amp;gt;                     ],
         3009        &amp;gt;                 },
         3010        &amp;gt;             ],
         3011        &amp;gt;         },
         3012        &amp;gt;     ],
         3013        &amp;gt; }
         3014        For full logs, run 'nix log /nix/store/j8wr6j1pvyf986sf74hqw8k31lvlzac5-toYAML.drv'.
         3015 &lt;/code&gt;&lt;/pre&gt;
         3016 &lt;p&gt;Yes, the output of &lt;code&gt;action-validator&lt;/code&gt; is awfully verbose, but it is still
         3017 better than making &lt;a href="https://x.com/eric_sink/status/1430954572848287744"&gt;&amp;quot;8 commits/push in one
         3018 hour&amp;quot;&lt;/a&gt;.&lt;/p&gt;
         3019 &lt;p&gt;If you are interested in how a more advantage usage of this technique is,
         3020 including usage of functions and constants to share common steps between
         3021 different actions, please take a look at the
         3022 &lt;a href="https://github.com/thiagokokada/nix-configs/tree/master/actions"&gt;actions&lt;/a&gt;
         3023 (&lt;a href="https://github.com/thiagokokada/nix-configs/tree/fbbc8f935cc7f774f691196e1612bbf7dd2054b5/actions"&gt;permalink&lt;/a&gt;)
         3024 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
         3025 Color&lt;/a&gt; made of completely new
         3026 parts for a friend: here is the
         3027 &lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;FPGBC&lt;/a&gt;.&lt;/p&gt;
         3028 &lt;p&gt;The &lt;em&gt;FP&lt;/em&gt; part of the name comes from
         3029 &lt;a href="https://en.wikipedia.org/wiki/Field-programmable_gate_array"&gt;FPGA&lt;/a&gt;, because
         3030 instead of software emulation this device use FPGA to reproduce the device.
         3031 While I am not convinced that FPGA is necessary more accurate than a good
         3032 software emulator, one advantage of FPGA is the (possible) lower input latency
         3033 thanks to the avoidance of complexity to handle the user input (e.g.: the
         3034 Operational System). A quick playthrough against &lt;a href="https://en.wikipedia.org/wiki/Motocross_Maniacs"&gt;Motocross
         3035 Maniacs&lt;/a&gt; seems to be fine, but
         3036 I can't see much difference from my &lt;a href="https://retrogamecorps.com/2022/05/15/miyoo-mini-v2-guide/"&gt;Miyoo
         3037 Mini+&lt;/a&gt; (I will do
         3038 more comparisons between the two devices later), that is a software emulation
         3039 device.&lt;/p&gt;
         3040 &lt;p&gt;But I think focusing in accuracy is wrong, the main reason of getting a device
         3041 like this one is for nostalgia, and this definitely hit the mark. The quality
         3042 of the case is as good as I remember the original, and most of the details are
         3043 replicate perfectly, including reproduction stickers in the back of the device.
         3044 The only differences that I can find is the usage of USB-C port for charging in
         3045 place of the barrel jack power adapter (thanks!), and the fact that the screen
         3046 bezels are smaller compared to the original (because the screen is bigger) and
         3047 doesn't include the Game Boy Color logo (that is fine in my opinion, since it
         3048 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
         3049 Cable&lt;/a&gt; (I don't have another
         3050 Game Boy to test). Sadly it is missing the infrared sensor, but the usage of
         3051 that was pretty limited anyway.&lt;/p&gt;
         3052 &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;
         3053 &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;
         3054 &lt;p&gt;So how well does it work? I can't say for sure. I don't have any original games
         3055 with me, so I am relying in backups and a
         3056 &lt;a href="https://en.wikipedia.org/wiki/Flashcard"&gt;flashcard&lt;/a&gt; for now. Many games that I
         3057 tested works fine, a few of them have graphical issues that can be fixed in the
         3058 menu (more about it later), and some of them doesn't boot. But I don't know if
         3059 the issue with the games not booting are because of the roms, the flashcard or
         3060 the device itself.&lt;/p&gt;
         3061 &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
         3062 GB&lt;/a&gt;. This FPGBC came with
         3063 firmware v1.09, while there is an update available for v1.10 in the
         3064 &lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;website&lt;/a&gt;. I had an weird issue in
         3065 the new firmware where no games would boot with this knockoff Everdrive so I
         3066 had to go back to v1.09, but again, I am not sure if the issue was fact that I
         3067 am using a knockoff device or this would happen with an original Everdrive GB.
         3068 If you are going to buy a proper Everdrive, you probably wouldn't get a
         3069 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
         3070 versions&lt;/a&gt;
         3071 have better compatibility with FPGBC.&lt;/p&gt;
         3072 &lt;p&gt;Sadly that the update didn't work, since there is this
         3073 &lt;a href="https://github.com/makhowastaken/GWGBC_FW"&gt;repository&lt;/a&gt; that patches the
         3074 firmware to boot the original logo instead of the ugly FPGBC one. And yes, for
         3075 some reason the v1.09 firmware from this repository still doesn't work with my
         3076 knockoff Everdrive.&lt;/p&gt;
         3077 &lt;p&gt;By the way, it seems the device is not easy to brick: I borked the firmware
         3078 update process once while trying to downgrade back to v1.09, resulting in a
         3079 black screen when I turned on the console. But just connecting the device to
         3080 the computer and powering on, I could flash the firmware again and the device
         3081 came back to life.&lt;/p&gt;
         3082 &lt;p&gt;About the features of the device: if you press the volume button (yes, you can
         3083 press it now), it opens the following menu:&lt;/p&gt;
         3084 &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;
         3085 &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
         3086 those, but the LCD screen seems to be IPS, and the quality is really good, and
         3087 also looks bright enough to play even under bad lightining conditions. And the
         3088 speaker has good quality, the sound is better than I remember, but sadly the
         3089 maximum volume is kind low. Still should be enough for playing in a quiet room.&lt;/p&gt;
         3090 &lt;p&gt;&lt;code&gt;DISPMOD&lt;/code&gt; is probably the most controversial option: it allow you to set which
         3091 scale you want. Anything with &lt;code&gt;EMU&lt;/code&gt; at the end means emulating the original
         3092 colors, and as far I remember it gets really close. You can also chose betwen
         3093 &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
         3094 image fills the whole screen at the cost of non-integer scaling. &lt;code&gt;X4&lt;/code&gt; is
         3095 integer scaling, however the image doesn't fill the whole screen. The &lt;code&gt;X4P&lt;/code&gt;
         3096 also includes a pixel effect that makes the image closer than the original
         3097 screen. It actually looks good, but the fact that I chose a white border for
         3098 this FPGBC makes the border really distracting. Maybe the black one is a better
         3099 choice if you want integer scale.&lt;/p&gt;
         3100 &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
         3101 Color). For those who don't know, you can run Game Boy games in Game Boy Color
         3102 and they will be automatically colorised. Some people don't like this and
         3103 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
         3104 to chose the color in GB mode, for example, the green-ish colors from the
         3105 original Game Boy or the blue-ish colors from &lt;a href="https://nintendo.fandom.com/wiki/Game_Boy_Light"&gt;Game Boy
         3106 Light&lt;/a&gt;. And yes, you can
         3107 choose the color palette for Game Boy games running in &lt;code&gt;GBC&lt;/code&gt; mode by pressing a
         3108 &lt;a href="https://gbstudiocentral.com/tips/game-boy-color-modes/"&gt;button combination&lt;/a&gt; at
         3109 the boot screen, but it seems not working in my unit and again, not sure if the
         3110 fault is my knockoff Everdrive.&lt;/p&gt;
         3111 &lt;p&gt;&lt;code&gt;FRAME_MIX&lt;/code&gt; basically is an option that makes some effects, like transparency
         3112 in &lt;a href="https://en.wikipedia.org/wiki/Wave_Race"&gt;Wave Race&lt;/a&gt;, to work at the cost of
         3113 introducing blurriness. The reason for this is that those effects depends in
         3114 the fact that the Game Boy screen was slow refresh, so you could rely on it by
         3115 rapidly changing pixels to create some interesting effects, but sadly those
         3116 effects doesn't work well in modern displays.&lt;/p&gt;
         3117 &lt;p&gt;&lt;code&gt;GB_CLRFIX&lt;/code&gt; is the option I mentioned before, where some Game Boy games just
         3118 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
         3119 Family&lt;/a&gt;. Turning
         3120 on fixes those games, but I am not sure if this option breaks other games.&lt;/p&gt;
         3121 &lt;p&gt;Finally, &lt;code&gt;SPD&lt;/code&gt; allows you to increase or decrease the CPU clock, slowing or
         3122 speeding up the games (including the sound). The result can be hilarious, so I
         3123 think this is a nice addition to the features. Sadly you can't know what the
         3124 default speed is, so you need to rely on sound to adjust back to the default.&lt;/p&gt;
         3125 &lt;p&gt;So in the end, can I recommend a FPGBC? I am not sure. If you want a device to
         3126 play games, I still think something like a Miyoo Mini+ is a better choice. Not
         3127 only you will have access to more games from different platforms, you also
         3128 don't need to rely on flashcards or cartridges. Also it has way more features
         3129 than FPGBC, like wireless multiplayer,
         3130 &lt;a href="https://retroachievements.org/"&gt;RetroArchivements&lt;/a&gt; and save states.&lt;/p&gt;
         3131 &lt;p&gt;But the actual reason to get a FPGBC is nostalgia, and for that I think the
         3132 FPGBC is difficult to beat. The price of the &lt;a href="https://funnyplaying.com/products/fpgbc-kit"&gt;kit to
         3133 assemble&lt;/a&gt; ($69.90) is cheaper than
         3134 most Game Boy's in good condition you can find in eBay, and you get for that
         3135 price a rechargable battery, an amazing quality screen, the PCB and the
         3136 speaker. You need to buy separately the case and the buttons, but in total you
         3137 will still end up paying less, and allows you to fully customise your build.
         3138 And the result device is not only in mint condition, it is really convenient
         3139 too: recharging batteries (via USB-C even) is much more convenient than buying
         3140 AA batteries, and the screen not only is better but it even has backlight. You
         3141 can also buy a fully built console for
         3142 &lt;a href="https://funnyplaying.com/products/fpgbc-console"&gt;$99.00&lt;/a&gt;, but you have less
         3143 options of customisation.&lt;/p&gt;
         3144 &lt;p&gt;This is the classic case of do what I say, don't do what I do. This FPGBC is a
         3145 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
         3146 Gold&lt;/a&gt; in (almost)
         3147 original hardware again.&lt;/p&gt;
         3148 &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
         3149 blog&lt;/a&gt; without I ever touching the
         3150 &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
         3151 script&lt;/a&gt; to Go
         3152 (&lt;a href="https://github.com/thiagokokada/blog/blob/3c39e0f7cd58b1af885f69871490b05bf6fc7d99/blog.go"&gt;permalink&lt;/a&gt;)
         3153 since my attempt to generate proper description to the RSS feed resulted in
         3154 slow startup times (not because of Python, but because of my usage of
         3155 &lt;code&gt;nix-shell&lt;/code&gt; since I didn't want to deal with
         3156 &lt;a href="https://docs.python.org/3/library/venv.html"&gt;venv&lt;/a&gt; or anything to manage my
         3157 Python dependencies).&lt;/p&gt;
         3158 &lt;p&gt;My previous workflow of this blog already involved me writing the texts in
         3159 &lt;a href="https://neovim.io/"&gt;neovim&lt;/a&gt;, copying and pasting the result in the
         3160 &lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; website and publishing. This was not
         3161 that bad, except that it seems I have a heavy tendency of editing my posts
         3162 multiple times. Copying and pasting data between neovim and the website became
         3163 tedious, so I decided to give up and automate the whole process.&lt;/p&gt;
         3164 &lt;p&gt;&lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt; (the blog platform
         3165 &lt;a href="https://capivaras.dev"&gt;capivaras.dev&lt;/a&gt; run) has a reasonable good
         3166 &lt;a href="https://mataroa.blog/api/docs/"&gt;API&lt;/a&gt;, and it only took a few hours to get a
         3167 version of publishing working (it would take less if
         3168 &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, the framework Mataroa is written, did
         3169 not have a weird behavior with URLs missing a trailing &lt;code&gt;/&lt;/code&gt;). An additional few
         3170 lines of
         3171 &lt;a href="https://github.com/thiagokokada/blog/blob/51b20612335c7f4312a51a0f436235b4b701ce8b/.github/workflows/go.yml"&gt;YAML&lt;/a&gt;
         3172 to make GitHub Actions trigger a pipeline and now I should never have to
         3173 manually update my blog again.&lt;/p&gt;
         3174 &lt;p&gt;I could have not done this. I mean, I probably wasted more time writing an
         3175 automation than I actually wasted publishing manually. But the manual process
         3176 is easy to miss, and I already did multiple mistakes publishing in the manual
         3177 method. For example, when writing the Markdown files, each post is written in a
         3178 particular format, where the first header is considered the title, so I need to
         3179 remove it from the contents during publication. But of course, this is easy to
         3180 miss, and I had to fix this multiple times already.&lt;/p&gt;
         3181 &lt;p&gt;So yes, I think this is a good lesson on why you should automate everything. It
         3182 is more than just about &lt;a href="https://xkcd.com/1205/"&gt;time savings&lt;/a&gt;, it is about
         3183 reducing mistakes and even documenting (even if poorly) a process. I mean, the
         3184 code I wrote is not that great, but I can definitely rely on it in the future
         3185 to remember what I need to do. It will be much faster than trying to learn from
         3186 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
         3187 got the hang of it. I first got interested in the language when I was in my
         3188 first job, between 2016-2018. At the time the language was a completely
         3189 different beast: no modules, no generics, no easy way to error wrap yet, etc.&lt;/p&gt;
         3190 &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
         3191 Go&lt;/a&gt;, wrote some scripts
         3192 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
         3193 library&lt;/a&gt; (with an &lt;a href="https://kokada.dev/blog/writing-nixos-tests-for-fun-and-profit"&gt;interesting
         3194 way to run
         3195 CI&lt;/a&gt;). I am also
         3196 writing more
         3197 &lt;a href="https://github.com/thiagokokada/nix-configs/blob/8c559527ed12e1d4f57a3fc5c72630b956f4c290/home-manager/desktop/wayland/hyprland/hyprtabs/hyprtabs.go"&gt;scripts&lt;/a&gt;
         3198 in the language, where I would prefer to use Bash or Python before. Heck, even
         3199 this blog is automatically published with a &lt;a href="https://kokada.dev/blog/quick-bits-why-you-should-automate-everything"&gt;Go
         3200 script&lt;/a&gt;,
         3201 that used to be a &lt;a href="https://kokada.dev/blog/using-github-as-a-bad-blog-platform"&gt;Python
         3202 one&lt;/a&gt; before. I can
         3203 say that nowadays it is another language in my toolbox, and while it is still a
         3204 love and hate relationship, recently it is more about love and less about hate.&lt;/p&gt;
         3205 &lt;p&gt;The points that I love about Go is probably obvious for some, but still
         3206 interesting to talk about anyway. The fact that the language generates static
         3207 binaries by default and have fast compilation times is something that I
         3208 apreciate since I first heard about the language, and now that I am using the
         3209 language frequently, are points thatI appreciate even more. Something about
         3210 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
         3211 main reason why I am using the language more frequently for scripts.&lt;/p&gt;
         3212 &lt;p&gt;Then we have the fast startup times. I am kind of sensitive to latency,
         3213 especially of command line utilities that need to answer fast when I expect
         3214 them to be fast (e.g.: &lt;code&gt;foo --help&lt;/code&gt;). This is one part where I could have
         3215 issues in Python, especially for more complex programs, but in Go it is rarely
         3216 an issue.&lt;/p&gt;
         3217 &lt;p&gt;Modules are also fantastic. It is not without its weirdness (like everything in
         3218 Go ecossystem), but the fact that it is so easy to add and manage dependencies
         3219 in a project using only the &lt;code&gt;go&lt;/code&gt; CLI is great. I also like that it generates a
         3220 hash of every dependency, making it reproducible (well, probably not at Nix
         3221 level, but still reproducible).&lt;/p&gt;
         3222 &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
         3223 can manage dependencies, generate documentation, format code, lint, run
         3224 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;,
         3225 check code for &lt;a href="https://go.dev/doc/articles/race_detector"&gt;races&lt;/a&gt; etc., all
         3226 with just the &amp;quot;compiler&amp;quot; for the language is excelent. Still probably one of
         3227 the best developer experiences I know in any programming language.&lt;/p&gt;
         3228 &lt;p&gt;I will not even talk about the things that everyone talks about Go, like
         3229 &lt;a href="https://go.dev/doc/effective_go#goroutines"&gt;goroutines&lt;/a&gt;, because I just don't
         3230 think I can add anything interesting to the topic.&lt;/p&gt;
         3231 &lt;p&gt;Now for the parts that I like less, the test part still quirks me that it is
         3232 not based in assertions, but thankfully it is easy to write assertions with
         3233 generics nowadays:&lt;/p&gt;
         3234 &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;
         3235 &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;
         3236 &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;
         3237 &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;
         3238 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         3239 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3240 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3241 &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;
         3242 &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;
         3243 &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;
         3244 &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;
         3245 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         3246 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3247 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3248 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// etc...
         3249 &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
         3250 about &lt;a href="https://github.com/stretchr/testify"&gt;testify&lt;/a&gt; and other assertion
         3251 libraries, but quoting &lt;a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;amp;t=568s"&gt;Rob
         3252 Pike&lt;/a&gt; here, &amp;quot;a little
         3253 copying is better than a little dependency&amp;quot;. As long the code you write is
         3254 trivial, it is better to duplicate the code than try to import a dependency.&lt;/p&gt;
         3255 &lt;p&gt;About another piece of code that generics allows me to write and I always end
         3256 up re-writing in every project is the &lt;code&gt;must*&lt;/code&gt; family of functions:&lt;/p&gt;
         3257 &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;
         3258 &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;
         3259 &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;
         3260 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         3261 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3262 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3263 &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;
         3264 &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;
         3265 &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;
         3266 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3267 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3268 &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;
         3269 &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;
         3270 &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;
         3271 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3272 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3273 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// must3, must4, etc...
         3274 &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
         3275 want to handle each error: if I have an error, I want the program to halt and
         3276 print a stack trace (exactly as I would have with a language with exceptions).
         3277 It basically allow me to convert code from:&lt;/p&gt;
         3278 &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;
         3279 &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;
         3280 &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;
         3281 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3282 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To:&lt;/p&gt;
         3283 &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;
         3284 &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
         3285 something great.&lt;/p&gt;
         3286 &lt;p&gt;Finally, for the things that I hate, well the biggest one currently is the lack
         3287 of nullability (or in Go terms,
         3288 &lt;a href="https://github.com/golang/go/issues/49202"&gt;nillability&lt;/a&gt;). After using
         3289 languages that has it, like Kotlin, or even something like
         3290 &lt;a href="https://www.mypy-lang.org/"&gt;mypy&lt;/a&gt;, this is one of those things that completely
         3291 changes the developer experience. I also still don't like the error handling
         3292 (but &lt;code&gt;must*&lt;/code&gt; goes far by improving the situation, when it is possible to use
         3293 it), especially because it is easy to lose context on it:&lt;/p&gt;
         3294 &lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// bad
         3295 &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;
         3296 &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;
         3297 &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;
         3298 &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;
         3299 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
         3300 &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;
         3301 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3302 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3303 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// good
         3304 &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;
         3305 &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;
         3306 &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;
         3307 &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;
         3308 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
         3309 &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;
         3310 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3311 &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
         3312 prefer immutability by default, but I find that in general as long as you split
         3313 your functions at a reasonable size it is generally fine.&lt;/p&gt;
         3314 &lt;p&gt;And for a language that is supposed to be straightforward, it is strange on how
         3315 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
         3316 &lt;code&gt;main&lt;/code&gt;&lt;/a&gt; packages, name capitalisation to
         3317 indicate visibility (&lt;code&gt;private&lt;/code&gt; vs &lt;code&gt;Public&lt;/code&gt;), conditional compiling by filenames
         3318 (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;),
         3319 etc.&lt;/p&gt;
         3320 &lt;p&gt;I expect to write more Go code going forward. Not because it is the perfect
         3321 language or whatever, but just because it a is language that has some really
         3322 good qualities that makes the language attractive even with the issues that I
         3323 have. That makes it a reasonable good language, and at least for me this is
         3324 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
         3325 ecosystem work, I just wanted to get it to run.&lt;/p&gt;
         3326 &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
         3327 many of you probably know, you can add &lt;code&gt;nix-shell&lt;/code&gt; as a shebang in your scripts
         3328 to run them as &lt;code&gt;./script&lt;/code&gt;. This was a PHP script so I wanted to do the same.
         3329 Easy right?&lt;/p&gt;
         3330 &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
         3331 &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
         3332 &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;
         3333 &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;
         3334 &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
         3335 &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
         3336 &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
         3337 &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
         3338 script if used. I removed &lt;code&gt;declare(strict_types=1)&lt;/code&gt; and while the script works,
         3339 I don't have enough expertise in PHP to know if this would be safe or not.&lt;/p&gt;
         3340 &lt;p&gt;I decided to try something that initially looked really dumb:&lt;/p&gt;
         3341 &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
         3342 &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;
         3343 &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;
         3344 &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
         3345 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And:&lt;/p&gt;
         3346 &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
         3347 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Works
         3348 &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;
         3349 &lt;p&gt;Eventually I found this
         3350 &lt;a href="https://github.com/NixOS/nix/issues/2570#issuecomment-446220517"&gt;comment&lt;/a&gt; in a
         3351 Nix issue talking about cases where &lt;code&gt;nix-shell&lt;/code&gt; shebang doesn't work. It looks
         3352 like the classic case of a &lt;a href="https://github.com/NixOS/nix/issues/2570#issuecomment-446222206"&gt;bug that becomes a
         3353 feature&lt;/a&gt;.&lt;/p&gt;
         3354 &lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; after posting this in
         3355 &lt;a href="https://lobste.rs/s/gkcgza/quick_bits_nix_shell_is_cursed"&gt;Lobte.rs&lt;/a&gt;, it seems
         3356 someone decided to open a &lt;a href="https://github.com/NixOS/nix/pull/11202"&gt;Pull
         3357 Request&lt;/a&gt; to document this behavior.
         3358 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
         3359 CLI&lt;/a&gt;
         3360 explicitly documents this behavior:&lt;/p&gt;
         3361 &lt;blockquote&gt;
         3362 &lt;p&gt;Note that the &lt;code&gt;#! nix&lt;/code&gt; lines don't need to follow after the first line, to
         3363 accomodate other interpreters.&lt;/p&gt;
         3364 &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
         3365 &lt;a href="https://gluer.org/"&gt;@ratsclub&lt;/a&gt; to give me free access to
         3366 &lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt;. But considering how small this blog
         3367 platform is supposed to be, I want to have at least somewhere to have a backup
         3368 of my posts. I know &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt;, the blog platform that
         3369 &lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt; runs, has automatic e-mail backups, but
         3370 I want something more reliable.&lt;/p&gt;
         3371 &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
         3372 supports&lt;/a&gt;) files inside
         3373 &lt;a href="https://neovim.io/"&gt;neovim&lt;/a&gt; anyway, so why not store all my Markdown files in
         3374 Git? So this is what I did, I now have an unofficial mirror in
         3375 &lt;a href="https://github.com/thiagokokada/blog"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
         3376 &lt;p&gt;While I am here, why not overcomplicate? Can I make an usable blog platform
         3377 from GitHub? And by that I don't mean GitHub pages, the repository itself. I
         3378 mean, it already renders Markdown files by default, so no need to do anything
         3379 in that space. To reach feature parity with
         3380 &lt;a href="https://capivaras.dev/"&gt;capivaras.dev&lt;/a&gt;, I only need to have an index and RSS
         3381 (since comments are not supported anyway). No need for newsletter since GitHub
         3382 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
         3383 feature&lt;/a&gt;
         3384 already.&lt;/p&gt;
         3385 &lt;p&gt;After a couple of hours hacking a Python script, you can see the result of this
         3386 monstrosity &lt;a href="https://github.com/thiagokokada/blog"&gt;here&lt;/a&gt;. The script, called
         3387 &lt;code&gt;gen_blog.py&lt;/code&gt;, is available at the same repository (here is a
         3388 &lt;a href="https://github.com/thiagokokada/blog/blob/c8986d1ab1b94c0986fd814629bb8eb4034fb6e7/gen_blog.py"&gt;permalink&lt;/a&gt;).
         3389 It automatically generates an index at
         3390 &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
         3391 each blog post and a
         3392 &lt;a href="https://raw.githubusercontent.com/thiagokokada/blog/main/rss.xml"&gt;&lt;code&gt;rss.xml&lt;/code&gt;&lt;/a&gt;
         3393 file at the root of the repository.&lt;/p&gt;
         3394 &lt;p&gt;Instead of trying to explain the code, I am going to explain the general idea,
         3395 because I think that if you want to replicate this idea it is better to rewrite
         3396 it in a way that you understand. It shouldn't take more than 2 hours in any
         3397 decent programming language. But if you really want, the script itself is
         3398 licensed in &lt;a href="https://en.wikipedia.org/wiki/WTFPL"&gt;WTFPL&lt;/a&gt; license. The code only
         3399 uses Python 3's standard library and should work in any relatively recent
         3400 version (anything newer than 3.9 should work).&lt;/p&gt;
         3401 &lt;p&gt;So the idea is basically to organise the repository and the Markdown files in a
         3402 easy way that makes it trivial to parse in a deterministic way. For example, my
         3403 repository is organised in the following way:&lt;/p&gt;
         3404 &lt;pre&gt;&lt;code&gt;.
         3405 ├── 2024-07-26
         3406 │   ├── 01-writing-nixos-tests-for-fun-and-profit.md
         3407 │   └── 02-using-github-as-a-bad-blog-platform.md &amp;lt;- this file
         3408 ├── gen_blog.py
         3409 ├── README.md
         3410 └── rss.xml
         3411 &lt;/code&gt;&lt;/pre&gt;
         3412 &lt;p&gt;Each day that you write a new blog post will be on its own directory. This is
         3413 nice because Markdown files may include extra files than the posts themselves,
         3414 e.g.: images, and this organisation make it trivial to organise everything.&lt;/p&gt;
         3415 &lt;p&gt;Each post has its own Markdown file. I put a two digit number before each post,
         3416 to ensure that when publishing multiple posts at the same day I keep them in
         3417 the same order of publishing. But if you don't care about it, you can just name
         3418 the files whatever you want.&lt;/p&gt;
         3419 &lt;p&gt;Also, I am assuming that each Markdown file has a header starting with &lt;code&gt;# &lt;/code&gt;,
         3420 and that is the title of the blog post.&lt;/p&gt;
         3421 &lt;p&gt;Using the above organisation, I have this function that scrapes the repository
         3422 and collect the necessary information to generate the index and RSS files:&lt;/p&gt;
         3423 &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;
         3424 &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;
         3425 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3426 &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;
         3427 &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;
         3428 &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;
         3429 &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;
         3430 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3431 &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;
         3432 &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;
         3433 &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;
         3434 &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;
         3435 &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;
         3436 &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;
         3437 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3438 &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;
         3439 &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;
         3440 &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;
         3441 &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;
         3442 &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;
         3443 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3444 &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;
         3445 &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;
         3446 &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;
         3447 &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;
         3448 &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;
         3449 &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;
         3450 &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;
         3451 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3452 &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;
         3453 &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
         3454 is a draft post, and ignore it from my scraper. I added a bunch of &lt;code&gt;WARN&lt;/code&gt;
         3455 prints to make sure that the me in the future doesn't do anything dumb. Also,
         3456 sorting in reverse since reverse chronological order is the one most people
         3457 expect in blogs (i.e.: more recent blog posts at top).&lt;/p&gt;
         3458 &lt;p&gt;After running the function above, I have a resulting dictionary that I can use
         3459 to generate either a &lt;code&gt;README.md&lt;/code&gt; file or Markdown:&lt;/p&gt;
         3460 &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;
         3461 &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;
         3462 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3463 &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;
         3464 &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;
         3465 &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;
         3466 &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;
         3467 &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;
         3468 &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;
         3469 &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;
         3470 &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;
         3471 &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;
         3472 &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;
         3473 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3474 &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;
         3475 &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;
         3476 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3477 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3478 &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;
         3479 &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;
         3480 &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;
         3481 &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;
         3482 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3483 &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;
         3484 &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;
         3485 &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;
         3486 &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;
         3487 &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;
         3488 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3489 &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;
         3490 &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;
         3491 &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;
         3492 &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;
         3493 &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;
         3494 &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;
         3495 &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;
         3496 &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;
         3497 &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;
         3498 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3499 &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;
         3500 &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;
         3501 &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;
         3502 &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;
         3503 &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;
         3504 &lt;blockquote&gt;
         3505 &lt;p&gt;README.md` at the root of the repository, and see the magic happen.&lt;/p&gt;
         3506 &lt;/blockquote&gt;
         3507 &lt;p&gt;It works much better than I initially anticipated. The &lt;code&gt;README.md&lt;/code&gt; is properly
         3508 populated with the titles and links. The RSS is kind of empty since it has no
         3509 description, but it seems to work fine (at least in
         3510 &lt;a href="https://www.inoreader.com/"&gt;Inoreader&lt;/a&gt;, my RSS reader of choice). I can
         3511 probably fill the post description with more information if I really want, but
         3512 it is enough for now (update: it is working now, you just need to render the
         3513 Markdown as HTML and escape the tags;
         3514 &lt;a href="https://github.com/thiagokokada/blog/blob/9506051cc3d49f203304174b335ff62de9d17a05/gen_blog.py"&gt;permalink&lt;/a&gt;
         3515 for the updated script). Not sure who is that interested in my writing that
         3516 will want to use this RSS feed instead the one available in
         3517 &lt;a href="https://kokada.capivaras.dev/rss/"&gt;capivaras.dev&lt;/a&gt; anyway.&lt;/p&gt;
         3518 &lt;p&gt;Also, while I am using GitHub here, the same idea would work in GitLab, Gitea,
         3519 sr.ht or whatever. As long as your source hub supports Markdown files it should
         3520 work.&lt;/p&gt;
         3521 &lt;p&gt;So that is it. I am not saying this is a good idea for your primary blog
         3522 platform or whatever, and I still prefer to publish to a platform that doesn't
         3523 track users or have tons of JavaScript or whatever. But if you want a backup of
         3524 your posts and you are already writing Markdown anyway, well, there are worse
         3525 ways to do it I think.&lt;/p&gt;
         3526 &lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; I rewrote the script again using Go
         3527 (&lt;a href="https://github.com/thiagokokada/blog/blob/3ece3853e7dd32977e7ec5238d2955e530ca4beb/blog.go"&gt;permalink&lt;/a&gt;).
         3528 The reason for it is because when I started rendering Markdown (for
         3529 descriptions) the Python version got quite slow (not the fault of Python
         3530 itself, mostly because of the usage of &lt;code&gt;nix-shell&lt;/code&gt; to manage dependencies;
         3531 something that Go doesn't need). Took about half an hour, showing how easy it
         3532 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
         3533 project&lt;/a&gt; writing an IPC library in
         3534 Go for &lt;a href="https://hyprland.org/"&gt;Hyprland&lt;/a&gt;, a Window Manager for Wayland.&lt;/p&gt;
         3535 &lt;p&gt;Once I got past the Work-in-Progress phase, I realise I had an issue: I wrote
         3536 some tests, but I was running then inside my system running Hyprland. And the
         3537 tests themselves were annoying: since they send commands to the current running
         3538 Hyprland instance, I was having programs being opened and settings being
         3539 changed, because this was the only way to have a reasonable good confidence
         3540 that what I was doing was correct. So I need to do like any good developer and
         3541 implement a CI, but how?&lt;/p&gt;
         3542 &lt;p&gt;One approach would be to create something like a mock client and test against
         3543 my mock. Since this mock wouldn't need a running Hyprland instance the tests
         3544 could run everywhere (even in non-Linux systems!), but they wouldn't be much
         3545 useful. Mocks are great for testing business logic, but not really for making
         3546 sure everything is working correctly.&lt;/p&gt;
         3547 &lt;p&gt;I need something more akin to an integration test, but this is tricky. It is
         3548 not like I am doing integration with e.g.: PostgreSQL that has thousands of
         3549 libraries available to make integration tests easier, I am doing integration
         3550 with a Window Manager that is a moving target with multiple breaking changes in
         3551 each release. And this is where NixOS tests enter, a way to run tests inside
         3552 Virtual Machines configured in Nix.&lt;/p&gt;
         3553 &lt;p&gt;I am a long time NixOS user and commiter, but I never wrote a NixOS test
         3554 outside of &lt;a href="https://github.com/NixOS/nixpkgs"&gt;nixpkgs&lt;/a&gt; itself. However I knew
         3555 it was possible, and after doing a quick reading of the &lt;a href="https://wiki.nixos.org/wiki/NixOS_VM_tests"&gt;Wiki
         3556 entry&lt;/a&gt; about it, I was ready to
         3557 start.&lt;/p&gt;
         3558 &lt;p&gt;The first part is to call &lt;code&gt;pkgs.testers.runNixOSTest&lt;/code&gt; and configure the machine
         3559 as any other NixOS system, e.g.:&lt;/p&gt;
         3560 &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;
         3561 &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;
         3562 &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;
         3563 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3564 &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;
         3565 &lt;/span&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;
         3566 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
         3567 &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;
         3568 &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;
         3569 &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;
         3570 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3571 &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;
         3572 &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;
         3573 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3574 &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;
         3575 &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;
         3576 &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;
         3577 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;};&lt;/span&gt;
         3578 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3579 &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;
         3580 &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;
         3581 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3582 &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;
         3583 &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;
         3584 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3585 &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;
         3586 &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;
         3587 &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;
         3588 &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;
         3589 &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;
         3590 &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;
         3591 &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;
         3592 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;];&lt;/span&gt;
         3593 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;};&lt;/span&gt;
         3594 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3595 &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;
         3596 &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;
         3597 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         3598 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3599 &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;
         3600 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3601 &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
         3602 is to setup things like a normal user account, add some extra packages we need
         3603 for testing, add Hyprland itself and configure auto-login. I have no idea how
         3604 painful it would be to automatise all those steps in e.g.: Ansible, but here we
         3605 are in a few lines of Nix code. This is, of course, thanks to all the
         3606 contributors to nixpkgs that implement something that help their own use case,
         3607 but once combined make it greater than the sum of the parts.&lt;/p&gt;
         3608 &lt;p&gt;Second is something that I took a while to figure out: how to enable GPU
         3609 acceleration inside the VM. You see, Hyprland, different from other Window
         3610 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
         3611 rendering via LLVMPipe, that while being slow since it is rendered in CPU, is
         3612 sufficient for this case.&lt;/p&gt;
         3613 &lt;p&gt;Putting the above code inside a
         3614 &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
         3615 something similar to:&lt;/p&gt;
         3616 &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;
         3617 &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;
         3618 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3619 &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;
         3620 &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;
         3621 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;};&lt;/span&gt;
         3622 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
         3623 &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;
         3624 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
         3625 &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;
         3626 &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;
         3627 &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;
         3628 &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;
         3629 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
         3630 &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;
         3631 &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;
         3632 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="p"&gt;};&lt;/span&gt;
         3633 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
         3634 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
         3635 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3636 &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
         3637 VM. However it is not really useful right now, since we didn't add any useful
         3638 code in &lt;code&gt;testScript&lt;/code&gt;, the core of the NixOS test framework. We can also run
         3639 &lt;code&gt;nix build .#checks.x86_64-linux.testVm.driverInteractive&lt;/code&gt; and
         3640 &lt;code&gt;./result/bin/nixos-test-driver&lt;/code&gt;: this will start a Python console where we can
         3641 manually play with the VM (try typing &lt;code&gt;start_all()&lt;/code&gt; for example).&lt;/p&gt;
         3642 &lt;p&gt;The &lt;code&gt;testScript&lt;/code&gt; is a sequence of Python statements that perform various
         3643 actions, such as starting VMs, executing commands in the VMs, and so on. More
         3644 about it in the official
         3645 &lt;a href="https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests"&gt;documentation&lt;/a&gt;.
         3646 For our case we can start with something like this:&lt;/p&gt;
         3647 &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;
         3648 &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;
         3649 &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()
         3650 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
         3651 &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;)
         3652 &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;)
         3653 &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;
         3654 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3655 &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
         3656 one, called &lt;code&gt;machine&lt;/code&gt;. We send two further commands to &lt;code&gt;machine&lt;/code&gt;:
         3657 &lt;code&gt;wait_for_unit(&amp;quot;multi-user.target&amp;quot;)&lt;/code&gt; and
         3658 &lt;code&gt;wait_for_file(&amp;quot;/home/alice/test-finished&amp;quot;)&lt;/code&gt;.&lt;/p&gt;
         3659 &lt;p&gt;The first command waits until systemd's &lt;code&gt;multi-user.target&lt;/code&gt; is ready, a good
         3660 way to ensure that the system is ready for further commands. The second one we
         3661 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,
         3662 a canary), but how can we generate this file?&lt;/p&gt;
         3663 &lt;p&gt;Remember that we added &lt;code&gt;programs.bash.loginShellInit = &amp;quot;Hyprland&amp;quot;&lt;/code&gt;, that
         3664 automatically starts Hyprland when Alice logs in. We need to modify that
         3665 command to run the Go tests from our library. The good thing is that Hyprland
         3666 configuration file supports a
         3667 &lt;a href="https://wiki.hyprland.org/Configuring/Keywords/#executing"&gt;&lt;code&gt;exec-once&lt;/code&gt;&lt;/a&gt;
         3668 command that runs a command during Hyprland launch. We can abuse this to launch
         3669 a terminal emulator and run our tests:&lt;/p&gt;
         3670 &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;
         3671 &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;
         3672 &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;
         3673 &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;
         3674 &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
         3675 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
         3676 &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
         3677 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;
         3678 &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
         3679 &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
         3680 &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;
         3681 &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;
         3682 &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;
         3683 &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;
         3684 &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;
         3685 &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;
         3686 &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;
         3687 &lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
         3688 &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
         3689 &lt;a href="https://sw.kovidgoyal.net/kitty/"&gt;Kitty&lt;/a&gt; terminal emulator, that then launches
         3690 a shell script that runs the test. Since we have no way to get the results of
         3691 the test, we pipe the output to a file that we can collect later (e.g.:
         3692 &lt;code&gt;machine.succeded(&amp;quot;cat /home/alice/test.log&amp;quot;)&lt;/code&gt;). And once the script exit, we
         3693 create the canary file &lt;code&gt;$HOME/test-finished&lt;/code&gt;, that allows the &lt;code&gt;testScript&lt;/code&gt;
         3694 knows that the test finished and it can destroy the VM safely.&lt;/p&gt;
         3695 &lt;p&gt;If you want to take a look at the final result, it is
         3696 &lt;a href="https://github.com/thiagokokada/hyprland-go/blob/v0.0.1/flake.nix"&gt;here&lt;/a&gt;. This
         3697 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
         3698 Actions&lt;/a&gt;
         3699 thanks to the the
         3700 &lt;a href="https://github.com/DeterminateSystems/nix-installer-action"&gt;nix-installer-action&lt;/a&gt;.&lt;/p&gt;
         3701 &lt;p&gt;And now I have a proper CI pipeline in a way that I never imagined would be
         3702