<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>iamnearlythere.com</title>
  <subtitle>iamnearlythere.com, a blog on software development</subtitle>
  <id>https://iamnearlythere.com/atom.xml</id>
  <updated>2025-03-19T22:20:00Z</updated>
  <link href="https://iamnearlythere.com/atom.xml" rel="self" type="application/atom+xml"></link>
  <link href="https://iamnearlythere.com/" rel="alternate" type="text/html"></link>
  <author>
    <name>Carl Helmertz</name>
    <email>helmertz@gmail.com</email>
  </author>
  <entry>
    <title>Bookmarklet for translating AWS account numbers to actual accounts</title>
    <published>2025-03-19T22:20:00Z</published>
    <updated>2025-03-19T22:20:00Z</updated>
    <id>https://iamnearlythere.com/bookmarklet-aws-account</id>
    <link href="https://iamnearlythere.com/bookmarklet-aws-account" rel="alternate" type="text/html" title="Bookmarklet for translating AWS account numbers to actual accounts"></link>
    <summary>Replacing HTML with a hardcoded list of replacements actually is useful</summary>
    <content type="html"><![CDATA[<p>I'm notoriously bad at remembering IP addresses, accounts, things like that, in my head. One thing that's recurring all over our systems at $WORK is AWS account numbers. They are referred to in logs, AWS' own interfaces, monitoring systems, docs/wikis etc. Since it's all web interfaces, we can create a simple solution for that!</p>
<p>This little bookmarklet crudely <em>replaces an account number with its hardcoded name</em>. This all happens locally, no risk of leakage. Hopefully, your AWS accounts doesn't change too often so that maintaining this hardcoded list of account numbers ↔ names, becomes a hassle.</p>
<p>Before clicking the bookmarklet:</p>
<p><img src="/assets/bookmarklet_aws_before.png" alt="AWS account selection menu, listing account numbers"></p>
<p>And after:</p>
<p><img src="/assets/bookmarklet_aws_after.png" alt="AWS account selection menu, having replaced account numbers with account names"></p>
<p>Here's the snippet:</p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span>javascript<span style="color:#000;font-weight:bold">:</span>(<span style="color:#000;font-weight:bold">function</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> bodyClass <span style="color:#000;font-weight:bold">=</span> <span style="color:#d14">&#34;__awsified&#34;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (<span style="color:#0086b3">document</span>.body.classList.contains(bodyClass)) {
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#0086b3">document</span>.body.classList.add(bodyClass);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> accounts <span style="color:#000;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#d14">&#34;123445945445&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;SWIMSUIT_FACTORY_PROD&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d14">&#34;456454354545&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;SWIMSUIT_FACTORY_TEST&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d14">&#34;425642694242&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;ZERO_COOL&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d14">&#34;312857138111&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;THE_PLAGUE&#34;</span>,
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> ids <span style="color:#000;font-weight:bold">=</span> <span style="color:#0086b3">Object</span>.keys(accounts).join(<span style="color:#d14">&#39;|&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> re <span style="color:#000;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">new</span> <span style="color:#0086b3">RegExp</span>(<span style="color:#d14">`\\b(</span><span style="color:#d14">${</span>ids<span style="color:#d14">}</span><span style="color:#d14">)\\b`</span>, <span style="color:#d14">&#39;g&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> spanStyle <span style="color:#000;font-weight:bold">=</span> <span style="color:#d14">&#39;font-weight: bold; background-color: yellow; color: black;&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">function</span> wrapMatchesInTextNode(textNode) {
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> text <span style="color:#000;font-weight:bold">=</span> textNode.textContent;
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> matches <span style="color:#000;font-weight:bold">=</span> [...text.matchAll(re)];
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">if</span> (matches.length <span style="color:#000;font-weight:bold">===</span> <span style="color:#099">0</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> frag <span style="color:#000;font-weight:bold">=</span> <span style="color:#0086b3">document</span>.createDocumentFragment();
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">let</span> lastIndex <span style="color:#000;font-weight:bold">=</span> <span style="color:#099">0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        matches.forEach(match =&gt; {
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">const</span> [matchedText] <span style="color:#000;font-weight:bold">=</span> match;
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">const</span> index <span style="color:#000;font-weight:bold">=</span> match.index;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">if</span> (index <span style="color:#000;font-weight:bold">&gt;</span> lastIndex) {
</span></span><span style="display:flex;"><span>                frag.appendChild(<span style="color:#0086b3">document</span>.createTextNode(text.slice(lastIndex, index)));
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">const</span> span <span style="color:#000;font-weight:bold">=</span> <span style="color:#0086b3">document</span>.createElement(<span style="color:#d14">&#39;span&#39;</span>);
</span></span><span style="display:flex;"><span>            span.textContent <span style="color:#000;font-weight:bold">=</span> accounts[matchedText];
</span></span><span style="display:flex;"><span>            span.setAttribute(<span style="color:#d14">&#39;title&#39;</span>, <span style="color:#d14">`Account ID: </span><span style="color:#d14">${</span>matchedText<span style="color:#d14">}</span><span style="color:#d14">`</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">if</span> (<span style="color:#0086b3">document</span>.body.classList.contains(bodyClass)) {
</span></span><span style="display:flex;"><span>                span.setAttribute(<span style="color:#d14">&#39;style&#39;</span>, spanStyle);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            frag.appendChild(span);
</span></span><span style="display:flex;"><span>            lastIndex <span style="color:#000;font-weight:bold">=</span> index <span style="color:#000;font-weight:bold">+</span> matchedText.length;
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">if</span> (lastIndex <span style="color:#000;font-weight:bold">&lt;</span> text.length) {
</span></span><span style="display:flex;"><span>            frag.appendChild(<span style="color:#0086b3">document</span>.createTextNode(text.slice(lastIndex)));
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        textNode.parentNode.replaceChild(frag, textNode);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">function</span> walkDOM(node) {
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">if</span> (node.nodeType <span style="color:#000;font-weight:bold">===</span> Node.TEXT_NODE) {
</span></span><span style="display:flex;"><span>            wrapMatchesInTextNode(node);
</span></span><span style="display:flex;"><span>        } <span style="color:#000;font-weight:bold">else</span> <span style="color:#000;font-weight:bold">if</span> (node.nodeType <span style="color:#000;font-weight:bold">===</span> Node.ELEMENT_NODE) {
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">const</span> tag <span style="color:#000;font-weight:bold">=</span> node.tagName.toLowerCase();
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">if</span> ([<span style="color:#d14">&#39;script&#39;</span>, <span style="color:#d14">&#39;style&#39;</span>, <span style="color:#d14">&#39;textarea&#39;</span>, <span style="color:#d14">&#39;input&#39;</span>, <span style="color:#d14">&#39;select&#39;</span>].includes(tag)) {
</span></span><span style="display:flex;"><span>                <span style="color:#000;font-weight:bold">return</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#000;font-weight:bold">for</span> (<span style="color:#000;font-weight:bold">let</span> i <span style="color:#000;font-weight:bold">=</span> <span style="color:#099">0</span>; i <span style="color:#000;font-weight:bold">&lt;</span> node.childNodes.length; i<span style="color:#000;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>                walkDOM(node.childNodes[i]);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    walkDOM(<span style="color:#0086b3">document</span>.body);
</span></span><span style="display:flex;"><span>})();
</span></span></code></pre>]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>Using Github issues for TODOs</title>
    <published>2025-02-17T14:33:00Z</published>
    <updated>2025-02-17T14:33:00Z</updated>
    <id>https://iamnearlythere.com/using-github-for-todos</id>
    <link href="https://iamnearlythere.com/using-github-for-todos" rel="alternate" type="text/html" title="Using Github issues for TODOs"></link>
    <summary>Easily create a lot of TODOs, that ends up in a consistent place in Github</summary>
    <content type="html"><![CDATA[<p>I've got an executable script named <code>todo</code> in my <code>$PATH</code>, and it looks like this:</p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span><span style="color:#999;font-weight:bold;font-style:italic">#!/bin/sh
</span></span></span><span style="display:flex;"><span><span style="color:#999;font-weight:bold;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># REPO formatted as user/repo</span>
</span></span><span style="display:flex;"><span><span style="color:#0086b3">test</span> -z <span style="color:#d14">&#34;</span><span style="color:#008080">$GH_TODO_REPO</span><span style="color:#d14">&#34;</span> <span style="color:#000;font-weight:bold">&amp;&amp;</span> <span style="color:#0086b3">echo</span> missing GH_TODO_REPO <span style="color:#000;font-weight:bold">&amp;&amp;</span> <span style="color:#0086b3">exit</span> <span style="color:#099">1</span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># PROJECT formatted in plaintext</span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># (I would skip the spaces, I think I had issues with that even when quoting)</span>
</span></span><span style="display:flex;"><span><span style="color:#0086b3">test</span> -z <span style="color:#d14">&#34;</span><span style="color:#008080">$GH_TODO_PROJECT</span><span style="color:#d14">&#34;</span> <span style="color:#000;font-weight:bold">&amp;&amp;</span> <span style="color:#0086b3">echo</span> missing GH_TODO_PROJECT <span style="color:#000;font-weight:bold">&amp;&amp;</span> <span style="color:#0086b3">exit</span> <span style="color:#099">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">[</span> -t <span style="color:#099">0</span> <span style="color:#000;font-weight:bold">]</span>; <span style="color:#000;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span>        <span style="color:#998;font-style:italic"># should be within a terminal</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#998;font-style:italic"># requires access rights: gh auth refresh -s project</span>
</span></span><span style="display:flex;"><span>        gh issue create --repo <span style="color:#d14">&#34;</span><span style="color:#008080">$GH_TODO_REPO</span><span style="color:#d14">&#34;</span> <span style="color:#d14">\
</span></span></span><span style="display:flex;"><span><span style="color:#d14"></span>                --project <span style="color:#d14">&#34;</span><span style="color:#008080">$GH_TODO_PROJECT</span><span style="color:#d14">&#34;</span> <span style="color:#d14">\
</span></span></span><span style="display:flex;"><span><span style="color:#d14"></span>                --assignee @me <span style="color:#d14">\
</span></span></span><span style="display:flex;"><span><span style="color:#d14"></span>                --milestone 2025:1 <span style="color:#d14">\
</span></span></span><span style="display:flex;"><span><span style="color:#d14"></span>                --editor
</span></span><span style="display:flex;"><span>        <span style="color:#008080">res</span><span style="color:#000;font-weight:bold">=</span><span style="color:#008080">$?</span>
</span></span><span style="display:flex;"><span>        <span style="color:#0086b3">exit</span> <span style="color:#008080">$res</span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#0086b3">echo</span> Launch through terminal
</span></span><span style="display:flex;"><span><span style="color:#0086b3">exit</span> <span style="color:#099">1</span>
</span></span></code></pre><p>Complementing that script, I have a clickable &quot;assigned to me&quot; todo-counter always visible in the status bar in my <a href="https://i3wm.org/">window manager</a>, courtesy of <a href="https://github.com/vivien/i3blocks">i3blocks</a>:</p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span><span style="color:#999;font-weight:bold;font-style:italic">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#999;font-weight:bold;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># --owner responds to the first part of the path in Github URLs,</span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># in this case, it&#39;s my employer</span>
</span></span><span style="display:flex;"><span><span style="color:#008080">count</span><span style="color:#000;font-weight:bold">=</span><span style="color:#000;font-weight:bold">$(</span>gh search issues --assignee @me --owner matchiapp --state open --json url --jq <span style="color:#d14">&#39;length&#39;</span><span style="color:#000;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># glyph from fontawesome, &#34;bug&#34; f188</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># long</span>
</span></span><span style="display:flex;"><span><span style="color:#0086b3">echo</span> <span style="color:#d14">&#34; </span><span style="color:#008080">$count</span><span style="color:#d14">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># short</span>
</span></span><span style="display:flex;"><span><span style="color:#0086b3">echo</span> <span style="color:#d14">&#34; </span><span style="color:#008080">$count</span><span style="color:#d14">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"># color</span>
</span></span><span style="display:flex;"><span><span style="color:#0086b3">echo</span> <span style="color:#d14">&#34;#00ff00&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>__open<span style="color:#000;font-weight:bold">()</span> <span style="color:#000;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>	<span style="color:#008080">url</span><span style="color:#000;font-weight:bold">=</span><span style="color:#008080">$1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#998;font-style:italic"># open the URL and tell i3 to focus the workspace,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#998;font-style:italic"># and use wmctrl to focus the correct window</span>
</span></span><span style="display:flex;"><span>	xdg-open <span style="color:#008080">$url</span> &amp;&gt;/dev/null <span style="color:#000;font-weight:bold">&amp;&amp;</span> i3-msg workspace number <span style="color:#099">2</span> &amp;&gt;/dev/null <span style="color:#000;font-weight:bold">&amp;&amp;</span> wmctrl -a firefox &amp;&gt;/dev/null
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">if</span> <span style="color:#000;font-weight:bold">[[</span> <span style="color:#d14">&#34;</span><span style="color:#008080">$BLOCK_BUTTON</span><span style="color:#d14">&#34;</span> -eq <span style="color:#099">1</span> <span style="color:#000;font-weight:bold">]]</span>; <span style="color:#000;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span>	<span style="color:#998;font-style:italic"># left click</span>
</span></span><span style="display:flex;"><span>	<span style="color:#998;font-style:italic"># &#34;my&#34; board</span>
</span></span><span style="display:flex;"><span>	__open https://github.com/orgs/matchiapp/projects/32
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">elif</span> <span style="color:#000;font-weight:bold">[[</span> <span style="color:#d14">&#34;</span><span style="color:#008080">$BLOCK_BUTTON</span><span style="color:#d14">&#34;</span> -eq <span style="color:#099">3</span> <span style="color:#000;font-weight:bold">]]</span>; <span style="color:#000;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span>	<span style="color:#998;font-style:italic"># right click</span>
</span></span><span style="display:flex;"><span>	<span style="color:#998;font-style:italic"># all of my issues</span>
</span></span><span style="display:flex;"><span>	__open https://github.com/issues/assigned
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">fi</span>
</span></span></code></pre><p>The above then renders like this:</p>
<p><img src="/assets/i3blocks_gh_todos.png" alt="i3blocks widget, a green bug icon and the number 20"></p>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>My slideshow presentation checklist</title>
    <published>2025-02-12T16:09:00Z</published>
    <updated>2025-02-12T16:09:00Z</updated>
    <id>https://iamnearlythere.com/slideshow-presentation-checklist</id>
    <link href="https://iamnearlythere.com/slideshow-presentation-checklist" rel="alternate" type="text/html" title="My slideshow presentation checklist"></link>
    <summary>A living document of things that helped me make a better presentation</summary>
    <content type="html"><![CDATA[<p>A couple of weeks ago, I presented how we could use a <a href="https://github.com/looplab/fsm">tool</a>, but failed to trigger any real discussion. Did I really reach out? Probably not.</p>
<p>To address this — instead of doing some ad-hoc screensharing of editor, alt-tab to code examples and browser windows — I figured I should self-organize a bit. Write down some criteria, and refine them after having tested them.</p>
<h2>Checklist</h2>
<ul>
<li>Sell a need, and then your solution to it.
<ul>
<li>Compare the past and the golden future.</li>
<li>No need to explore every non-chosen path, but be prepared for questions.</li>
</ul>
</li>
<li>Red thread, from start to end. Control the arc.
<ul>
<li>What's the general message, who are you presenting for.
<ul>
<li>It's easiest to avoid audiences of too broad backgrounds, to be able to zoom in on a topic and create a discourse.</li>
</ul>
</li>
</ul>
</li>
<li>Few takeaways per slide.
<ul>
<li>No lists/multiple paragraphs at a time. Or, bring in one bullet at a time on the same page.
<ul>
<li>Avoid lists, everyone is on different points and it is hard to read and listen at the same time.</li>
</ul>
</li>
<li>Avoid a reading audience, better to write a few words and focus on talking.</li>
</ul>
</li>
<li>Use a proper slideshow tool.
<ul>
<li>Everyone is familiar with the concept. Seeing &quot;pagination&quot; progression is helpful.</li>
<li>Answering questions &amp; discussions during the presentation is super simple, since we get right back into the presentation where we left off.</li>
</ul>
</li>
<li>Large font size (helps small monitors, poor eyesight, crappy resolutions in large crowds), few words per page (helps balance reading vs listening).</li>
<li>Clear path forward in real life, next steps to take action on.
<ul>
<li>Also helps validate the idea.</li>
<li>Could be written first.</li>
</ul>
</li>
<li>Repeat takeaways.
<ul>
<li>What's the one thing the audience show bring with them?</li>
<li>Copy &amp; paste problem statement slide with checkboxes for each sub-problem</li>
</ul>
</li>
<li>Give credits where credits are due.</li>
</ul>
<h2>Actual example</h2>
<p>Today, I held another presentation (on ephemeral dev. envs). I prepared using the above points, and it was much better received - lots of questions timed together with the topic of the current slide, etc.</p>
<p>The actual slides came out like this:</p>
<ol>
<li>Title</li>
<li>Problems now: 1 .., 2 .., 3 ..</li>
<li>Solution: x</li>
<li>Demo screenrecording</li>
<li>Explain what we just saw, in detail</li>
<li>Rehash problems from 2., with solutions</li>
<li>Next steps/outro/questions/credits</li>
</ol>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>Getting Cursor to start on Ubuntu 24.04</title>
    <published>2025-02-06T11:57:00Z</published>
    <updated>2025-02-06T11:57:00Z</updated>
    <id>https://iamnearlythere.com/cursor-ubuntu-2404</id>
    <link href="https://iamnearlythere.com/cursor-ubuntu-2404" rel="alternate" type="text/html" title="Getting Cursor to start on Ubuntu 24.04"></link>
    <summary>Modify apparmor rules a bit</summary>
    <content type="html"><![CDATA[<p>There seems to be a problem with appimages including electron on Ubuntu 24.04. When starting cursor (0.45.8),  you get error messages like this:</p>
<pre><code>feb 06 11:37:24 gamma kernel: audit: type=1400 audit(1738838244.309:315): apparmor=&quot;AUDIT&quot; operation=&quot;userns_create&quot; class=&quot;namespace&quot; info=&quot;Userns create - transitioning profile&quot; profile=&quot;unconfined&quot; pid=28305 comm=&quot;cursor&quot; requested=&quot;userns_create&quot; target=&quot;unprivileged_userns&quot;
feb 06 11:37:24 gamma kernel: audit: type=1400 audit(1738838244.310:316): apparmor=&quot;DENIED&quot; operation=&quot;capable&quot; class=&quot;cap&quot; profile=&quot;unprivileged_userns&quot; pid=28316 comm=&quot;cursor&quot; capability=21  capname=&quot;sys_admin&quot;
feb 06 11:37:24 gamma /usr/libexec/gdm-x-session[28305]: [28305:0206/113724.318413:FATAL:setuid_sandbox_host.cc(163)] The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. You need to make sure that /tmp/.mount_cursorbCLhGT/chrome-sandbox is owned by root and has mode 4755.
feb 06 11:37:24 gamma kernel: traps: cursor[28305] trap int3 ip:5d79fb77726c sp:7fffe2cdbd60 error:0 in cursor[5d79f77ea000+8974000]
feb 06 11:37:24 gamma /usr/libexec/gdm-x-session[28304]: Trace/breakpoint trap (core dumped)
feb 06 11:37:24 gamma systemd[1]: tmp-.mount_cursorbCLhGT.mount: Deactivated successfully.
</code></pre>
<p>To get around this, I created /etc/apparmor.d/cursor with this content:</p>
<pre><code>abi &lt;abi/4.0&gt;,
include &lt;tunables/global&gt;

profile cursor /usr/bin/cursor flags=(unconfined) {
    userns,

    # Site-specific additions and overrides. See local/README for details.
    include if exists &lt;local/cursor&gt;
}
</code></pre>
<p>Note that I renamed the appimage file to /usr/bin/cursor, and <code>chmod +x</code> it.</p>
<p>After reloading the apparmor configuration with</p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span>sudo apparmor_parser -r /etc/apparmor.d/cursor
</span></span></code></pre><p>I could start cursor properly.</p>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>VS Code snippets for internal blog links</title>
    <published>2025-01-01T23:33:00Z</published>
    <updated>2025-01-01T23:33:00Z</updated>
    <id>https://iamnearlythere.com/vscode-snippets-for-internal-blog-links</id>
    <link href="https://iamnearlythere.com/vscode-snippets-for-internal-blog-links" rel="alternate" type="text/html" title="VS Code snippets for internal blog links"></link>
    <summary>Linking to other blog posts is only a few keys away</summary>
    <content type="html"><![CDATA[<p>I wanted to quickly link between blog posts. Since I use VS Code, we can abuse <a href="https://code.visualstudio.com/docs/editor/userdefinedsnippets">user defined snippets</a> to support this feature.</p>
<p>This is the end result:</p>
<video controls>
  <source src="https://github.com/user-attachments/assets/02d53ef2-d89e-4afd-a720-9662e25ab230" type="video/mp4" />
</video>
<h2>Generating the snippets in VS Code's JSON format</h2>
<p>After some experimentation, this is the target format:</p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#000080">&#34;A few system design \u0026 architecture experiences, low-level debugging stories and post-mortems&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;scope&#34;</span>: <span style="color:#d14">&#34;markdown&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;prefix&#34;</span>: <span style="color:#d14">&#34;@@interesting-reads - A few system design \u0026 architecture experiences, low-level debugging stories and post-mortems&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;body&#34;</span>: <span style="color:#d14">&#34;[${1:A few system design \u0026 architecture experiences, low-level debugging stories and post-mortems}](/interesting-reads) &#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;description&#34;</span>: <span style="color:#d14">&#34;A few system design \u0026 architecture experiences, low-level debugging stories and post-mortems&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#000080">&#34;A grep-quickie for the road (or svn, really)&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;scope&#34;</span>: <span style="color:#d14">&#34;markdown&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;prefix&#34;</span>: <span style="color:#d14">&#34;@@a-grep-quickie-for-the-road-or-svn-really - A grep-quickie for the road (or svn, really)&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;body&#34;</span>: <span style="color:#d14">&#34;[${1:A grep-quickie for the road (or svn, really)}](/a-grep-quickie-for-the-road-or-svn-really) &#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;description&#34;</span>: <span style="color:#d14">&#34;A grep-quickie for the road (or svn, really)&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#998;font-style:italic">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>}
</span></span></code></pre><p>Good ideas:</p>
<ul>
<li>Put the generated snippets file in  <code>.vscode/posts.code-snippets</code> of your blog's repository.
<ul>
<li>Keeping it local to a git repository keeps everything nice and tidy. No suggestions for your other repositories.</li>
</ul>
</li>
<li>Use a common starting string, <code>@@</code>, for all snippets' <code>&quot;prefix&quot;</code>.
<ul>
<li>We want some way to only get suggestions of blog posts.</li>
</ul>
</li>
<li>Spam keywords in <code>&quot;prefix&quot;</code>.
<ul>
<li>VS Code's fuzzy auto completion only matches against <code>&quot;prefix&quot;</code>. <code>&quot;description&quot;</code> is decoration only.</li>
</ul>
</li>
<li>Provide a default for the link text (to the blog post title).
<ul>
<li>In body text, the link text will almost always be removed, but having a writing prompt is not too shabby.</li>
<li>VS Code smartly pre-selects the default, so there's no cost of just starting to write something completely different.</li>
</ul>
</li>
<li>Put a trailing space in <code>&quot;body&quot;</code>.
<ul>
<li>Allows for quickly continuing to write. If the link is at the end of a sentence, you need to follow up with a backspace. So be it.</li>
</ul>
</li>
</ul>
<p><a href="https://github.com/chelmertz/chelmertz.github.io/commit/9bf5b41ccba352ed91450316b638d2f6a3bda6b9#diff-94984348a77639388f82ce61d30baf29dd5177f404e5a7a22dc3e2945d9ec5e2R360-R382">All it takes is a loop to generate the snippets</a>, which is done while generating the HTML of the blog posts.</p>
<p>By regenerating the snippets for each build of the blog contents, we make sure
that the snippets' URLs &amp; titles are up to date.</p>
<h2>Postscript, on motivation</h2>
<p>I'm trying to get over my private org mode file usage and &quot;blog&quot; more.  <code>wc</code>
reports 240k words, in comparison to only three blog posts between 2013 and
2024.</p>
<p>One part of this effort was to make the blog engine a bit more lightweight and
custom, <a href="/replacing-Jekyll-with-go">so I wrote a new one</a>.</p>
<p>Another part was to find a quick way to refer to sibling blog posts here, when
writing markdown.</p>
<p>Zettelkasten (small notes and a giant web of internal links) seems to be super
popular. There are many plugins supporting this, it sounds a bit like
<a href="https://vimdoc.sourceforge.net/htmldoc/editing.html#gf"><code>gf</code></a> in vim), but I
figured that each markdown post's <code>permalink</code> would be unique &amp; stable enough to
use as a target.</p>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>Upgrading to Ubuntu 24.04</title>
    <published>2024-12-20T00:29:00Z</published>
    <updated>2024-12-20T00:29:00Z</updated>
    <id>https://iamnearlythere.com/ubuntu-2404</id>
    <link href="https://iamnearlythere.com/ubuntu-2404" rel="alternate" type="text/html" title="Upgrading to Ubuntu 24.04"></link>
    <summary>Not everything went super smoothly</summary>
    <content type="html"><![CDATA[<p>Some issues I ran into:</p>
<ul>
<li><code>man</code> pages were no longer colored.
<ul>
<li>Workaround: <a href="https://bbs.archlinux.org/viewtopic.php?id=287185"><code>export GROFF_NO_SGR=1</code></a>.</li>
</ul>
</li>
<li>Switching system theme with <code>gsettings set org.gnome.desktop.interface color-scheme prefer-dark</code> no longer takes effect.
<ul>
<li>No workaround as of yet.</li>
<li>Lot's of <code>xdg-portal</code> related packages installed and configured back and forth, to no avail (yet).</li>
<li>Adding <code>i3</code> to <code>UseIn=gnome;i3</code> in /usr/share/xdg-desktop-portal/portals/gtk.portal didn't help, reverted.</li>
<li>Adding <code>org.freedesktop.impl.portal.Settings=gtk</code> in ~/.config/xdg-desktop-portal/portals.conf didn't help, reverted.</li>
<li><code>XDG_CURRENT_DESKTOP</code> is &quot;i3&quot;, and <code>XDG_DESKTOP_PORTAL</code> is &quot;&quot;</li>
<li>There's an arch wiki page on <a href="https://wiki.archlinux.org/title/XDG_Desktop_Portal">XDG Desktop Portal</a></li>
<li>I've got (not sure who owns this change) .config/systemd/user/xdg-desktop-portal.service.d/override.conf
<pre><code>[Service]
Environment=GTK_USE_PORTAL=1
</code></pre>
<ul>
<li>Adding <code>Environment=GDK_DEBUG=portals</code> didn't help, reverted.</li>
</ul>
</li>
</ul>
</li>
<li>Running a simple Ansible playbook no longer works.
<ul>
<li>Missing python package, Ubuntu no longer likes <code>pip3</code>. Good, in a way; especially language-specific packages should not really be installed globally.</li>
<li>Tried using <code>uv</code> for the first time, didn't find the correct invocation.</li>
<li><code>ansible-galaxy collection install community.general</code> solved a snap error, unsure what other tries contributed to it working.</li>
</ul>
</li>
<li>Volume up/down buttons didn't work.
<ul>
<li>Installing <code>pulseaudio</code> &quot;fixed&quot; it. It was supposed to be a &quot;temporary solution&quot;, it probably will be permanent. I guess <code>pipewire</code> just needs some configuration and things probably work by accident currently.</li>
</ul>
</li>
<li><code>gedit</code> is no longer a thing.
<ul>
<li><code>gnome-text-editor</code> is apparently the new thing.</li>
</ul>
</li>
<li>Gnome Console (kgx) renders an extra symbol for dead keys (like ~ or `) even when &quot;completed&quot;
<ul>
<li>Both in the shell and in e.g. vim</li>
<li>Switched to <a href="https://wezfurlong.org/wezterm/">WezTerm</a> which works well enough.</li>
</ul>
</li>
<li><a href="https://github.com/phillipberndt/autorandr">autorandr</a> seems to be borked:
<ul>
<li>journalctl:
<pre><code>jan 07 19:40:43 gamma autorandr[1909]: /usr/bin/autorandr:210: SyntaxWarning: invalid escape sequence '\s'
jan 07 19:40:43 gamma autorandr[1909]:   XRANDR_OUTPUT_REGEXP = &quot;&quot;&quot;(?x)
jan 07 19:40:43 gamma autorandr[1909]: /usr/bin/autorandr:236: SyntaxWarning: invalid escape sequence '\s'
jan 07 19:40:43 gamma autorandr[1909]:   &quot;&quot;&quot; + XRANDR_PROPERTIES_REGEXP + &quot;&quot;&quot; |                                      # Properties to include in the profile
jan 07 19:40:43 gamma autorandr[1909]: /usr/bin/autorandr:248: SyntaxWarning: invalid escape sequence '\S'
jan 07 19:40:43 gamma autorandr[1909]:   XRANDR_OUTPUT_MODES_REGEXP = &quot;&quot;&quot;(?x)
jan 07 19:40:43 gamma autorandr[1909]: /usr/bin/autorandr:592: SyntaxWarning: invalid escape sequence '\s'
jan 07 19:40:43 gamma autorandr[1909]:   version = re.search(&quot;xrandr program version\s+([0-9\.]+)&quot;, version_string).group(1)
jan 07 19:40:43 gamma autorandr[1909]: /usr/bin/autorandr:888: SyntaxWarning: invalid escape sequence '\+'
jan 07 19:40:43 gamma autorandr[1909]:   match = re.match(&quot;(?P&lt;w&gt;[0-9]+)x(?P&lt;h&gt;[0-9]+)(?:\+(?P&lt;x&gt;[0-9]+))?(?:\+(?P&lt;y&gt;[0-9]+))?.*&quot;, output.options[&quot;panning&quot;])
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Deactivated successfully.
jan 07 19:40:43 gamma systemd[1]: Finished autorandr.service - autorandr execution hook.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Start request repeated too quickly.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Failed with result 'start-limit-hit'.
jan 07 19:40:43 gamma systemd[1]: Failed to start autorandr.service - autorandr execution hook.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Start request repeated too quickly.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Failed with result 'start-limit-hit'.
jan 07 19:40:43 gamma systemd[1]: Failed to start autorandr.service - autorandr execution hook.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Start request repeated too quickly.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Failed with result 'start-limit-hit'.
jan 07 19:40:43 gamma systemd[1]: Failed to start autorandr.service - autorandr execution hook.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Start request repeated too quickly.
jan 07 19:40:43 gamma systemd[1]: autorandr.service: Failed with result 'start-limit-hit'.
jan 07 19:40:43 gamma systemd[1]: Failed to start autorandr.service - autorandr execution hook.
</code></pre>
</li>
<li>When getting power only through my Monitor's USB-C, autorandr prevented the GDM (login screen) to render. Fun times.</li>
<li>The changelog of 1.15 says &quot;Several regex literal bug fixes&quot; and my version was 1.14-2 (the latest provided by my default repositories).</li>
</ul>
</li>
</ul>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>Uses this - my preferred tools</title>
    <published>2024-12-11T18:00:00Z</published>
    <updated>2024-12-11T18:00:00Z</updated>
    <id>https://iamnearlythere.com/uses-this</id>
    <link href="https://iamnearlythere.com/uses-this" rel="alternate" type="text/html" title="Uses this - my preferred tools"></link>
    <summary>Tooling that I rely on, using Linux on the desktop as a software engineer</summary>
    <content type="html"><![CDATA[<p><a href="https://github.com/chelmertz/dotfiles">github.com/chelmertz/dotfiles</a> contains all dotfiles.</p>
<p><a href="https://github.com/chelmertz/dotfiles/blob/master/ansible-laptop.yml">My ansible file</a> (<ins datetime="2025-12-31" cite="https://github.com/chelmertz/dotfiles/commit/09c8affe45a16371d35ac2619e136adea6dcdcee">and now also my <a href="https://github.com/chelmertz/dotfiles/blob/master/nix/home.nix">nix home-manager config</a></ins>) could be used to look for sources &amp; dependencies.</p>
<h2>Desktop applications</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Purpose &amp; context</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://christian.amsuess.com/tools/arandr/">Arandr</a></td>
<td>Configuring multi-monitor setup</td>
</tr>
<tr>
<td><a href="https://gitlab.gnome.org/GNOME/console">Console</a></td>
<td>A good enough terminal. Supporting dark/light mode is critical</td>
</tr>
<tr>
<td><a href="https://hluk.github.io/CopyQ/">Copyq</a></td>
<td>Clipboard</td>
</tr>
<tr>
<td><a href="https://github.com/doomemacs/doomemacs">Doom Emacs</a></td>
<td>Purely as entry point to Org mode/Magit</td>
</tr>
<tr>
<td><a href="https://www.dropbox.com">Dropbox</a></td>
<td>Sync between mobile and laptop is a must</td>
</tr>
<tr>
<td><a href="https://element.io/">Element</a></td>
<td>Matrix chat client</td>
</tr>
<tr>
<td><a href="https://espanso.org">Espanso</a></td>
<td>Snippet expander</td>
</tr>
<tr>
<td><a href="https://github.com/chelmertz/elly">Elly</a></td>
<td>Github pull request tracker</td>
</tr>
<tr>
<td><a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a></td>
<td>I hear Chrome is mucking about with adblockers</td>
</tr>
<tr>
<td><a href="https://flameshot.org/">Flameshot</a></td>
<td>Screenshots with very convenient UI for quick editing</td>
</tr>
<tr>
<td><a href="https://www.jetbrains.com/idea/">Intellij IDEA</a></td>
<td>Editor for java and grails (don't ask)</td>
</tr>
<tr>
<td><a href="https://www.libreoffice.org/">Libreoffice</a></td>
<td>Quick &amp; dirty editing of truth tables/decision tables and CSV files</td>
</tr>
<tr>
<td><a href="https://lwks.com/">Lightshot</a></td>
<td>Video editor - OK for stitching together things but more complex than my needs</td>
</tr>
<tr>
<td><a href="https://magit.vc/">Magit</a></td>
<td>The best git client. See <em>lazygit</em></td>
</tr>
<tr>
<td><a href="https://obsidian.md/">Obsidian</a></td>
<td>Purely as entry point to Excalidraw</td>
</tr>
<tr>
<td><a href="https://orgmode.org/">Org mode</a></td>
<td>Diary. TODOs.</td>
</tr>
<tr>
<td><a href="https://github.com/phw/peek">Peek</a></td>
<td>Screen recorder</td>
</tr>
<tr>
<td><a href="https://github.com/jonls/redshift">Redshift</a></td>
<td>Adjusts color temperature of screen</td>
</tr>
<tr>
<td><a href="https://github.com/davatorium/rofi">Rofi</a></td>
<td>Launcher. Easy to customize. Like fzf but without a terminal in the foreground</td>
</tr>
<tr>
<td><a href="https://www.thregr.org/wavexx/software/screenkey/">Screenkey</a></td>
<td>Displays the pressed keys on screen. Useful but not perfect with multiple monitors</td>
</tr>
<tr>
<td><a href="https://code.visualstudio.com/">VS code</a></td>
<td>Code editor</td>
</tr>
<tr>
<td><a href="https://i3wm.org/">i3</a></td>
<td>Window manager</td>
</tr>
<tr>
<td><a href="https://github.com/vivien/i3blocks">i3blocks</a></td>
<td>Task bar for i3</td>
</tr>
</tbody>
</table>
<h2>Languages &amp; libraries</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Purpose &amp; context</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://go.dev/">Go</a></td>
<td>Programming in general</td>
</tr>
<tr>
<td><a href="https://github.com/looplab/fsm">fsm</a></td>
<td>Go package for encoding state machines. Exports mermaid diagrams.</td>
</tr>
<tr>
<td><a href="https://github.com/google/go-cmp">cmp</a></td>
<td>Go package for reducing test writing efforts</td>
</tr>
<tr>
<td><a href="https://github.com/oapi-codegen/oapi-codegen">oapi-codegen</a></td>
<td>Go package enabling spec-first OpenAPI-development</td>
</tr>
<tr>
<td><a href="https://github.com/pressly/goose">goose</a></td>
<td>DB migration in golang</td>
</tr>
<tr>
<td><a href="https://github.com/testcontainers/testcontainers-go">Testcontainers</a></td>
<td>Handles ephemeral environments of otherwise tricky dependencies (i.e. temporary postgres instances)</td>
</tr>
<tr>
<td><a href="https://connectrpc.com/">connectrpc</a></td>
<td>grpc and protobuf bridge for json</td>
</tr>
<tr>
<td><a href="https://www.gocd.org/">GoCD</a></td>
<td>CD worth trying out. Github actions is not my favorite</td>
</tr>
</tbody>
</table>
<h2>CLI &amp; TUI</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Purpose &amp; context</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/sharkdp/bat">bat</a></td>
<td>Nicer <code>cat</code></td>
</tr>
<tr>
<td><a href="https://github.com/AlDanial/cloc">cloc</a></td>
<td>Counts LoC in codebases</td>
</tr>
<tr>
<td><a href="https://github.com/chelmertz/dotfiles/blob/master/bin/datedate">datedate</a></td>
<td>Transforms dates &amp; timestamps</td>
</tr>
<tr>
<td><a href="http://duc.zevv.nl/">duc</a></td>
<td>Another disk usage tool. Sunburst chart</td>
</tr>
<tr>
<td><a href="https://github.com/dunst-project/dunst">dunst</a></td>
<td>Notifications</td>
</tr>
<tr>
<td><a href="https://github.com/eradman/entr">entr</a></td>
<td>inotify-like watcher. Execute command when files change</td>
</tr>
<tr>
<td><a href="https://github.com/sharkdp/fd">fd</a></td>
<td>Less verbose <code>find</code></td>
</tr>
<tr>
<td><a href="https://github.com/junegunn/fzf">fzf</a></td>
<td>Fuzzy finder</td>
</tr>
<tr>
<td><a href="https://github.com/dundee/gdu">gdu</a></td>
<td>A fast disk usage analyser</td>
</tr>
<tr>
<td><a href="https://cli.github.com/">gh</a></td>
<td>Github via CLI. Usable for quickly entering TODOs with some attributes and for listing open issues and displaying with i3blocks</td>
</tr>
<tr>
<td><a href="https://github.com/newren/git-filter-repo">git-filter-repo</a></td>
<td>Can extract directory of git repo to its own repo</td>
</tr>
<tr>
<td><a href="https://github.com/charmbracelet/glow">glow</a></td>
<td>Markdown viewer</td>
</tr>
<tr>
<td><a href="https://github.com/tomnomnom/gron">gron</a></td>
<td>Turns JSON into grep:able lines of text and back again</td>
</tr>
<tr>
<td><a href="https://htop.dev/">htop</a></td>
<td>Good enough system monitor</td>
</tr>
<tr>
<td><a href="https://github.com/jgalat/image-sorter/">image-sorter</a></td>
<td>TUI for quickly moving image files into proper directories</td>
</tr>
<tr>
<td><a href="https://github.com/jesseduffield/lazygit">lazygit</a></td>
<td>Very similar to Magit. Slightly less intuitive but working in the terminal without all of Emacs is more comfy.</td>
</tr>
<tr>
<td><a href="https://github.com/dbcli/litecli">litecli</a></td>
<td>Better CLI for sqlite. See <em>mycli</em></td>
</tr>
<tr>
<td><a href="https://joeyh.name/code/moreutils/">moreutils</a></td>
<td>sponge, vipe, ts are proper CLI tools</td>
</tr>
<tr>
<td><a href="https://github.com/dbcli/mycli">mycli</a></td>
<td>Better CLI for mysql. See <em>litecli</em></td>
</tr>
<tr>
<td><a href="https://pandoc.org/">pandoc</a></td>
<td>Document converter</td>
</tr>
<tr>
<td><a href="https://freedesktop.org/software/pulseaudio/pavucontrol/">pavucontrol</a></td>
<td>Choosing device for sound output</td>
</tr>
<tr>
<td><a href="https://github.com/loveencounterflow/pspg">pspg</a></td>
<td>Pager for tabular content like <code>psql</code> or CSV files</td>
</tr>
<tr>
<td><a href="https://github.com/nik012003/ripdrag">ripdrag</a></td>
<td>“ripdrag filename” creates a small window with “filename” that you can use for drop-friendly interfaces</td>
</tr>
<tr>
<td><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a></td>
<td>More ergonomic search than grep</td>
</tr>
<tr>
<td><a href="https://github.com/chelmertz/serve">serve</a></td>
<td>Serve directory via HTTP</td>
</tr>
<tr>
<td><a href="https://github.com/akavel/up">up</a></td>
<td>Pipe commands dynamically</td>
</tr>
<tr>
<td><a href="https://github.com/watchexec/watchexec">watchexec</a></td>
<td>inotify-like watcher. Execute command when files change. Slightly more complete than <code>entr</code> - might overtake its position</td>
</tr>
<tr>
<td><a href="https://linux.die.net/man/1/wmctrl">wmctrl</a></td>
<td>API capabilities for X.org</td>
</tr>
<tr>
<td><a href="https://github.com/alols/xcape">xcape</a></td>
<td>Rebind keys. For example: pressing caps=esc &amp; holding caps=ctrl</td>
</tr>
<tr>
<td><a href="https://github.com/rupa/z">z</a></td>
<td>Interface to MRU directories when <code>cd</code>:ing around</td>
</tr>
<tr>
<td><a href="https://help.gnome.org/users/zenity/stable/">zenity</a></td>
<td>Interactive bash scripts. Very useful with i3blocks</td>
</tr>
<tr>
<td><a href="https://github.com/epistates/treemd">treemd</a></td>
<td>TUI markdown navigator</td>
</tr>
<tr>
<td><a href="https://github.com/ddworken/hishtory">Hishtory</a></td>
<td>Shell history with better ctrl+r and an actual design</td>
</tr>
<tr>
<td><a href="https://runme.dev/">runme</a></td>
<td>Executes runbooks defined in markdown</td>
</tr>
<tr>
<td><a href="https://github.com/hadolint/hadolint">hadolint</a></td>
<td>Dockerfile linter</td>
</tr>
<tr>
<td><a href="https://github.com/chubin/wttr.in">wttr.in</a></td>
<td>Weather report</td>
</tr>
</tbody>
</table>
<h2>Web applications &amp; third party services</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Purpose &amp; context</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://asciinema.org/">Asciinema</a></td>
<td>Record terminal interactions</td>
</tr>
<tr>
<td><a href="https://app.diagrams.net/">Draw.io</a></td>
<td>Diagrams</td>
</tr>
<tr>
<td><a href="https://excalidraw.com/">Excalidraw</a></td>
<td>Diagrams</td>
</tr>
<tr>
<td><a href="https://github.com/brendangregg/FlameGraph">FlameGraph</a></td>
<td>Hierarchical profile viewer</td>
</tr>
<tr>
<td><a href="https://github.com/features/actions">Github actions</a></td>
<td>CI/CD</td>
</tr>
<tr>
<td><a href="https://ilograph.com/">Ilograph</a></td>
<td>Great alternative to mermaid, draw.io, excalidraw; many good concepts like multiple perspectives of a single source</td>
</tr>
<tr>
<td><a href="https://mermaid.js.org/">Mermaid</a></td>
<td>Diagrams</td>
</tr>
<tr>
<td><a href="https://www.notion.so/">Notion</a></td>
<td>Wiki</td>
</tr>
<tr>
<td><a href="https://numbr.dev/">Numbr</a></td>
<td>Adds up numbers in plaintext. Good for splitting costs</td>
</tr>
<tr>
<td><a href="https://www.shellcheck.net/">Shellcheck</a></td>
<td>Lints bash scripts</td>
</tr>
<tr>
<td><a href="https://uptime.kuma.pet/">Uptime Kuma</a></td>
<td>Simple monitoring tool</td>
</tr>
<tr>
<td><a href="https://pagefind.app/">Pagefind</a></td>
<td>Embeddable search engine (with index)</td>
</tr>
</tbody>
</table>
<h2>Browser extensions</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Purpose &amp; context</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://addons.mozilla.org/en-GB/android/addon/consent-o-matic/">consent-o-matchi</a></td>
<td>Anti cookie-popups</td>
</tr>
<tr>
<td><a href="https://darkreader.org/">dark reader</a></td>
<td>Automatic dark mode</td>
</tr>
<tr>
<td><a href="https://addons.mozilla.org/en-US/firefox/addon/grasp/">grasp</a></td>
<td>Bookmarks current page into an org file</td>
</tr>
<tr>
<td><a href="https://languagetool.org/">languagetool</a></td>
<td>Effortless spell- &amp; grammar checking everywhere</td>
</tr>
<tr>
<td><a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/">uBlock origin</a></td>
<td>Blocks ads</td>
</tr>
<tr>
<td><a href="https://addons.mozilla.org/en-US/firefox/addon/vimium-ff/">vimium</a></td>
<td>Vim bindings when browsing</td>
</tr>
</tbody>
</table>
<h2>Hardware</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Purpose &amp; context</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://www.keychron.com/products/keychron-k8-pro-qmk-via-wireless-mechanical-keyboard?_pos=1&amp;_psq=k8+pro&amp;_ss=e&amp;_v=1.0&amp;variant=40558016757849">Keychron K8 Pro</a></td>
<td>Very comfortable keyboard</td>
</tr>
<tr>
<td>Logitech G MX518</td>
<td>So happy when this got revived</td>
</tr>
</tbody>
</table>
<h2>On the radar</h2>
<p>Untried but interesting. Hammers looking for the proper nails.</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Purpose &amp; context</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/wader/ansisvg">ansisvg</a></td>
<td>Capture terminal output into svg for presentations</td>
</tr>
<tr>
<td><a href="https://github.com/Tuurlijk/apisnip">apisnip</a></td>
<td>Concise overview of endpoints in an OpenAPI specification</td>
</tr>
<tr>
<td><a href="https://github.com/Canop/broot">broot</a></td>
<td>Interactive tree/ls/cd</td>
</tr>
<tr>
<td><a href="https://github.com/wagoodman/dive">dive</a></td>
<td>Docker image layer introspection</td>
</tr>
<tr>
<td><a href="https://github.com/glacambre/firenvim">firenvim</a></td>
<td>vim in browser textareas</td>
</tr>
<tr>
<td><a href="https://github.com/wader/fq">fq</a></td>
<td>jq for binary files</td>
</tr>
<tr>
<td><a href="https://github.com/tummychow/git-absorb">git-absorb</a></td>
<td>Aids fixups in git</td>
</tr>
<tr>
<td><a href="https://github.com/mystor/git-revise">git-revise</a></td>
<td>Split commits in git</td>
</tr>
<tr>
<td><a href="https://github.com/itchyny/gojo">gojo</a></td>
<td>CLI for constructing JSON</td>
</tr>
<tr>
<td><a href="https://github.com/gristlabs/grist-core">grist</a></td>
<td>Next generation Spreadsheet (probably like Airtable)</td>
</tr>
<tr>
<td><a href="https://github.com/blacknon/hwatch">hwatch</a></td>
<td>hwatch, watch alternative that records the history and keeps diffs</td>
</tr>
<tr>
<td><a href="https://github.com/sharkdp/hyperfine">hyperfine</a></td>
<td>Benchmarks</td>
</tr>
<tr>
<td><a href="https://sr.ht/~gpanders/ijq/">ijq</a></td>
<td>Interactive version of jq</td>
</tr>
<tr>
<td><a href="https://immich.app/">immich</a></td>
<td>Self-hosted Google Photos alternative</td>
</tr>
<tr>
<td><a href="https://github.com/isd-project/isd">isd</a></td>
<td>TUI for systemd</td>
</tr>
<tr>
<td><a href="https://github.com/martinvonz/jj">jujutsu</a></td>
<td>VCS on top of git</td>
</tr>
<tr>
<td><a href="https://kuzudb.com/">kuzudb</a></td>
<td>Embeddable graph database. Maybe valid alternative to sqlite</td>
</tr>
<tr>
<td><a href="https://github.com/jesseduffield/lazydocker">lazydocker</a></td>
<td>TUI for docker. See <em>lazygit</em></td>
</tr>
<tr>
<td><a href="https://github.com/axllent/mailpit">mailpit</a></td>
<td>Fake SMTP server with web interface</td>
</tr>
<tr>
<td><a href="https://modsecurity.org/">modsecurity</a></td>
<td>Open source WAF</td>
</tr>
<tr>
<td><a href="https://www.openpolicyagent.org/">open policy agent</a></td>
<td>Policy engine</td>
</tr>
<tr>
<td><a href="https://github.com/evilsocket/opensnitch">opensnitch</a></td>
<td>Application firewall</td>
</tr>
<tr>
<td><a href="https://github.com/sharkdp/pastel">pastel</a></td>
<td>CLI for color palettes</td>
</tr>
<tr>
<td><a href="https://plantuml.com/">plantuml</a></td>
<td>Diagrams</td>
</tr>
<tr>
<td><a href="https://podman.io/">podman</a></td>
<td>Podman, docker alternative</td>
</tr>
<tr>
<td><a href="https://terminaltrove.com/pspg/">pspg</a></td>
<td>Pager for tabular data</td>
</tr>
<tr>
<td><a href="https://pushover.net/">pushover</a></td>
<td>Push notifications to a personal device for $5 for a lifetime</td>
</tr>
<tr>
<td><a href="https://sqids.org/">sqids</a></td>
<td>Short youtube-like IDs that you could spell out</td>
</tr>
<tr>
<td><a href="https://github.com/sqldef/sqldef">sqldef</a></td>
<td>SQL migration tool for sqlite et al.</td>
</tr>
<tr>
<td><a href="https://github.com/nferraz/st">st</a></td>
<td>Statistics on CLI</td>
</tr>
<tr>
<td><a href="https://github.com/marionebl/svg-term-cli">svg-term-cli</a></td>
<td>Record terminal session as svg + css</td>
</tr>
<tr>
<td><a href="https://github.com/syncthing/syncthing">syncthing</a></td>
<td>Dropbox alternative</td>
</tr>
<tr>
<td><a href="https://learn.temporal.io/getting_started/go/first_program_in_go/">temporal</a></td>
<td>Distributed computing with error handling</td>
</tr>
<tr>
<td><a href="https://github.com/Genivia/ugrep">ugrep</a></td>
<td>Interactive grep</td>
</tr>
<tr>
<td><a href="https://github.com/akavel/up">up</a></td>
<td>Ultimate plumber. Interactive pipelines</td>
</tr>
<tr>
<td><a href="https://uppy.io/">uppy</a></td>
<td>Javascript library for uploads, including screencast on the fly or Dropbox et al.</td>
</tr>
<tr>
<td><a href="https://webhook.site/">webhook.site</a></td>
<td>Hosted/one-off webhooks</td>
</tr>
<tr>
<td><a href="https://github.com/sxyazi/yazi">yazi</a></td>
<td>File manager TUI</td>
</tr>
<tr>
<td><a href="https://github.com/red-data-tools/YouPlot">youplot</a></td>
<td>Plotting library for CLI</td>
</tr>
<tr>
<td><a href="https://github.com/woodruffw/zizmor">zizmor</a></td>
<td>Audits Github actions</td>
</tr>
<tr>
<td><a href="https://github.com/joehillen/sysz">zsys</a></td>
<td>fzf TUI for systemctl</td>
</tr>
<tr>
<td><a href="https://github.com/quickwit-oss/tantivy">tantivy</a></td>
<td>Search engine - alternative to Elasticsearch or Solr</td>
</tr>
<tr>
<td><a href="https://doc.dxuuu.xyz/prr/tutorial.html">prr</a></td>
<td>Review Github PRs locally</td>
</tr>
<tr>
<td><a href="https://github.com/medialab/xan">xan</a></td>
<td>CSV editor/viewer</td>
</tr>
<tr>
<td><a href="https://github.com/alecthomas/t">t</a></td>
<td>Language that replaces awk/cut/sed...</td>
</tr>
<tr>
<td><a href="https://github.com/bk138/gromit-mpx">gromit-mpx</a></td>
<td>Draw on screen</td>
</tr>
<tr>
<td><a href="https://github.com/abenz1267/walker">walker</a></td>
<td>rofi alternative (launcher)</td>
</tr>
<tr>
<td><a href="https://kdl.dev/">KDL</a></td>
<td>Config language</td>
</tr>
<tr>
<td><a href="https://github.com/containers/bubblewrap">bubblewrap</a></td>
<td>sandbox processes</td>
</tr>
<tr>
<td><a href="https://dagger.io/">Dagger</a></td>
<td>Local orchestration for building and testing</td>
</tr>
</tbody>
</table>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>A few system design &amp; architecture experiences, low-level debugging stories and post-mortems</title>
    <published>2024-12-04T23:13:00Z</published>
    <updated>2024-12-04T23:13:00Z</updated>
    <id>https://iamnearlythere.com/interesting-reads</id>
    <link href="https://iamnearlythere.com/interesting-reads" rel="alternate" type="text/html" title="A few system design &amp; architecture experiences, low-level debugging stories and post-mortems"></link>
    <summary>A bunch of different sources one could learn from</summary>
    <content type="html"><![CDATA[<p>A living document of some nice real life dev experiences, documented elsewhere on the internet:</p>
<table>
<thead>
<tr>
<th>Title</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://calpaterson.com/bank-python.html">Bank python</a></td>
<td>Simple &amp; accessible interfaces for everything. Seemingly the anti-thesis of clean code</td>
</tr>
<tr>
<td><a href="https://www.brendangregg.com/blog/2021-09-26/the-speed-of-time.html">Brendan Gregg: The Speed of Time</a></td>
<td>Flamegraphs discovers reading current time took longer on Ubuntu than CentOS</td>
</tr>
<tr>
<td><a href="https://www.youtube.com/watch?v=ahyk7h7qAF4">How Instagram Reduced Web Page Load by 50% </a></td>
<td>Many optimization patterns for front-end, from HTTP transportation, to asset fetching order, to caching locally (and avoiding conflicts with new state updates)</td>
</tr>
<tr>
<td><a href="https://www.youtube.com/watch?v=TdhXPsDXdAI">How Instagram Scaled to 14 Million Users With Only 3 Engineers </a></td>
<td>Postgres: pgbouncer, ID strategy for finding correct shard, (stateless) Django, Gearman, etc.</td>
</tr>
<tr>
<td><a href="https://www.youtube.com/watch?v=HruBoinmPBA">How Notion Scaled to 100 Million Users Without Their Database Exploding </a></td>
<td>Doubling down on Postgres, scaling horizontally using shards and pgbouncer</td>
</tr>
<tr>
<td><a href="https://garciat.com/posts/java-adt/">Java Pattern: Algebraic Data Types</a></td>
<td>Using the visitor pattern to mimic ADT in a relatively readable manner, despite Java™</td>
</tr>
<tr>
<td><a href="https://corecursive.com/lisp-in-space-with-ron-garret/">LISP in Space</a></td>
<td>Debugging hardware in orbit</td>
</tr>
<tr>
<td><a href="https://www.gov.uk/service-manual/agile-delivery/making-services-in-an-emergency">Making services in an emergency</a></td>
<td>UK government guidelines on how to build a servce when time is of the essence</td>
</tr>
<tr>
<td><a href="https://gist.github.com/reborg/dc8b0c96c397a56668905e2767fd697f">Rich Already Answered That!</a></td>
<td>Rich Hickey discusses the design of Clojure in depth</td>
</tr>
<tr>
<td><a href="https://www.youtube.com/watch?v=3Ea3pkTCYx4">Unix and Google</a></td>
<td>'Coding at the perimiter', an approach to tackle the n^3 matrix of system ✕ feature ✕ platform - write programs that work together (pipes, everything-is-a-file, etc.). Examplifies with Multics vs UNIX</td>
</tr>
<tr>
<td><a href="https://www.perfecttableplan.com/html/genetic_algorithm.html">Using A Genetic Algorithm For Table Seating</a></td>
<td>A solver with an interesting UI: choose between a timeout or a certain number of generations</td>
</tr>
<tr>
<td><a href="https://rachelbythebay.com/w/2025/02/22/war/">War rooms vs. deep investigations</a></td>
<td>A debugging journey lasting a couple of weeks, of a Facebook crash</td>
</tr>
<tr>
<td><a href="https://buttondown.com/hillelwayne/archive/edge-case-poisoning/">Edge Case Poisoning</a></td>
<td>Formalizing recipes is hard</td>
</tr>
<tr>
<td><a href="https://roscidus.com/blog/blog/categories/0install/">0install move to OCaml from Python</a></td>
<td>Comparison of a lot of languages</td>
</tr>
<tr>
<td><a href="https://nesbitt.io/2025/12/26/how-uv-got-so-fast.html">How uv got so fast</a></td>
<td>The design of a package manager</td>
</tr>
</tbody>
</table>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>Replacing Jekyll with Go</title>
    <published>2024-12-01T11:58:00Z</published>
    <updated>2024-12-01T11:58:00Z</updated>
    <id>https://iamnearlythere.com/replacing-Jekyll-with-go</id>
    <link href="https://iamnearlythere.com/replacing-Jekyll-with-go" rel="alternate" type="text/html" title="Replacing Jekyll with Go"></link>
    <summary>Jekyll was too annoying and now wouldn&#39;t start - replacing it all with Go and a couple of libraries took a couple of hours</summary>
    <content type="html"><![CDATA[<p>My old dockerized <a href="https://jekyllrb.com/">Jekyll</a> instance started breaking, and
keeping the blog boring but built in a totally different way seemed like a good
way to spend some quality time.</p>
<p>The end result became <a href="https://github.com/chelmertz/chelmertz.github.io/blob/2b54b9fd13b68cba9098929535eb878e1c974056/cmd/blog/blog.go">a Go
file</a>
that does things in a simple way - a saving clause from said file: &quot;hardcoded,
idempotent, blunt error handling, not incremental, not parallel, duplicated data
in memory&quot;.</p>
<p>This is a small devlog:</p>
<ul>
<li>Github pages &quot;just works&quot; with Jekyll, when moving to non-Jekyll I:
<ul>
<li>&quot;Deploy from the <em>main branch</em>&quot;; maintaining separate branches is a personal
no-no, for a small personal project, <a href="https://trunkbaseddevelopment.com/">trunk-based
development</a> is king.</li>
<li>&quot;Deploy from a <em>folder</em>&quot;; pre-generating &amp; checking in things lets you know
what you get (<a href="https://github.com/chelmertz/serve">just spin up a local HTTP
server</a>), compared to crossing your
fingers everytime Github's Jekyll integration takes over.
<ul>
<li>I read the docs on <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site">DNS
configuration</a>
in the middle of the night, while making changes &amp; waiting for TTLs to
expire, but what finally worked: <em>have a CNAME file in the <code>docs/</code> folder,
even if you're using A records</em>.</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://github.com/chelmertz/chelmertz.github.io/blob/2b54b9fd13b68cba9098929535eb878e1c974056/cmd/blog/blog.go">The new blog &quot;script&quot;</a> was written iteratively, fixing lots of papercuts along the way.
<ul>
<li>As an example: sometime around 2012, my version of Jekyll at the time,
started to add a <code>date:</code> in the frontmatter, rather than having it as part
of the filename. There were several such cutoffs (<code>permalink:</code> not always
being there, ... etc.).
<ul>
<li>If I were developing Jekyll, I would instead use a migration strategy to
bring all files up to date, and then never having to worry about the old
format again (like.. every RDBMS migration library), but instead Jekyll
seemed to have kept the complexity of &quot;keep every old version working&quot;
within their tool. What Jekyll's codebase looks like with that kind of
approach, I don't want to know.</li>
</ul>
</li>
<li>Fixing one issue at a time, such as &quot;parse publication date from filename
<em>or</em> frontmatter <code>date:</code>&quot;, let me:
<ol>
<li>View a successfully rendered blog quicker.</li>
<li>Amend the markdown posts with a more complete version of frontmatter
properties.</li>
<li><a href="https://github.com/chelmertz/chelmertz.github.io/commit/9908552b27e5ad4b1005afaf514af59a994651d5">Throw out the &quot;reparation&quot; code branches and replace them with a
validation step</a>.</li>
</ol>
</li>
</ul>
</li>
<li>Go's backwards compatibility story will hopefully prove this right, in a
couple of years from now.</li>
<li>I am missing a single feature of jekyll: <a href="https://github.com/jekyll/jekyll/blob/0e4182aefad27c72c6b1c0f0e300e57edefaa0ba/lib/jekyll/excerpt.rb#L98-L145">extracting
excerpts</a>,
that are put in the generated HTML's <code>&lt;meta name=&quot;description&quot;/&gt;</code> and the items
of the Atom feed's <code>&lt;summary&gt;</code>.
<ul>
<li>I just put this as a manual thing to write as an optional <code>summary:</code> front
matter property, which would probably create a much better result in the
end.</li>
</ul>
</li>
</ul>
<p>Here are some sed lines for working with frontmatter, notably the &quot;only work
within the delimiter lines&quot;, which was news to me.</p>
<p><strong>Removing slashes in <code>permalink:</code></strong></p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span>sed -i -e <span style="color:#d14">&#39;/^---$/,/^---$/ {/^\s*permalink: /s/\///g}&#39;</span> _posts/*md
</span></span></code></pre><p><strong>Removing empty lines in frontmatter:</strong></p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span>sed -i <span style="color:#d14">&#39;/^---$/,/^---$/ {/^\s*$/d}&#39;</span> _posts/*md
</span></span></code></pre><p><strong>Removing a frontmatter property:</strong></p>
<pre tabindex="0" style="background-color:#fff;"><code><span style="display:flex;"><span>sed -i <span style="color:#d14">&#39;/^---$/,/^---$/ {/^\s*comments:/d}&#39;</span> _posts/*md
</span></span></code></pre><p>And here's a paper trail of what broke the camel's back:</p>
<pre><code>...
Error reading file /srv/Jekyll/_layouts/post.html: No such file or directory @ rb_sysopen - /srv/jekyll/my_site/srv/jekyll/_layouts/post.html 
Error reading file /srv/Jekyll/_layouts/default.html: No such file or directory @ rb_sysopen - /srv/jekyll/my_site/srv/jekyll/_layouts/default.html 
Error reading file /srv/Jekyll/_layouts/page.html: No such file or directory @ rb_sysopen - /srv/jekyll/my_site/srv/jekyll/_layouts/page.html 
Error reading file /srv/Jekyll/_layouts/home.html: No such file or directory @ rb_sysopen - /srv/jekyll/my_site/srv/jekyll/_layouts/home.html
</code></pre>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
  <entry>
    <title>image-sorter patches</title>
    <published>2024-09-08T15:37:00Z</published>
    <updated>2024-09-08T15:37:00Z</updated>
    <id>https://iamnearlythere.com/image-sorter-patches</id>
    <link href="https://iamnearlythere.com/image-sorter-patches" rel="alternate" type="text/html" title="image-sorter patches"></link>
    <summary>Contributing to image-sorter, that helps manual categorization of images</summary>
    <content type="html"><![CDATA[<p><a href="https://github.com/jgalat/image-sorter/">image-sorter</a> is a smart program that
lets you organize your images.</p>
<p>You give it a couple of directories to look for images in, and some alternative
directories those images should move to. When viewing a &quot;to be moved&quot; image
inside the program window, you press a shortcut for the best matching output
directory, and move on to the next image.</p>
<p>Besides having a smart way of configuring the bindings to the output
directories, it also persists the &quot;move&quot; commands in a shell file, for you to
inspect before executing it. This is <a href="https://en.wikipedia.org/wiki/Dry_run_(testing)">dry
run</a> being builtin and the
default, and it's so smart to me.</p>
<p>I tried adding a couple of patches, and luckily for me, the author was very
friendly and accepted them:</p>
<ul>
<li><a href="https://github.com/jgalat/image-sorter/commit/40ac1649abee3384c9149de8e694d8210c391ba2">fix: Guarantee key mapping sort order</a></li>
<li><a href="https://github.com/jgalat/image-sorter/commit/0b79ab2051db057ff1433b87cce3c5c8e23ad972">fix(path): Expand ~ (tilde) in target paths</a></li>
<li><a href="https://github.com/jgalat/image-sorter/commit/d72c98decb7b52f299d8ddd64980fe5f2b832c64">feat(delete): Delete an image with backspace</a></li>
<li><a href="https://github.com/jgalat/image-sorter/commit/ccfb588400386fbde6a70bbd38f77ce23e55f579">feat: Recurse into target paths if -r is given</a></li>
<li><a href="https://github.com/jgalat/image-sorter/commit/1ac60977a2a1444d66c439766dfb723398fc6318">chore: Decorate errors with useful information</a></li>
<li><a href="https://github.com/jgalat/image-sorter/commit/1532584003acce3d30f4f6f8034bb6debae60c03">fix(filetype): infer after checking file ext</a></li>
<li><a href="https://github.com/jgalat/image-sorter/commit/cbfb4033c787bbd19c38deaf74162e805f41f0f7">feat: Try to shorten directory to fit into preview</a></li>
</ul>
<p>Besides some very small contributions at work, these are the first patches I've
written in Rust. Last time I tried writing rust with vscode-out-of-the-box
settings, the process was very slow (auto completion and type checking took
very much time, not ideal when coding). For this size of project, the tooling
was super quick, so I could just enjoy the nice things about Rust.</p>
<p>Some things to keep in mind for the next excursion with Rust:</p>
<ul>
<li>Don't overuse the catch-all <code>_</code> in pattern matching. When adding new (for
example) enum cases, you want to get compiler errors to fix.</li>
<li>Use linters. <code>cargo fmt --all -- --check</code> and <code>cargo clippy --all-targets --all-features -- -D warnings</code> were used in this project, and they offer good
hints à la &quot;yes, your code compiles but <em>this</em> is more semantic&quot;.</li>
<li><a href="https://docs.rs/structopt/latest/structopt/">structopt</a> was nice for CLI
parsing, I'm sure <a href="https://docs.rs/clap/latest/clap/">clap</a> is good, too.</li>
<li>The dependencies in rust are so many and so small (and I added more of them
🙈).</li>
</ul>
<hr>
<p>And also, a debugging experience:</p>
<p>I wanted to add a feature for looking through folders recursively for images.
Once the code was written, and I tested it, the program just died. This was due
to the alignment of some nice error conditions:</p>
<ol>
<li>&quot;Is it an image or not&quot; was checked by file type and nothing else.</li>
<li>The directory being looked through, contained node_modules somewhere in the
file tree. One of those node_modules contained an invalid image file
(basically <code>echo hello&gt;a.jpg</code>) as a test case. Because, of course, having a
dependency in node means importing the whole repository.</li>
<li>The TUI library code swallowed error messages, in order to control what's
rendered by the client.</li>
</ol>
]]></content>
    <author>
      <name>Carl Helmertz</name>
      <email>helmertz@gmail.com</email>
    </author>
  </entry>
</feed>