<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:source="https://source.scripting.com/" xml:lang="en">
  <title>Stefan VanBuren · All Content</title>
  <link href="https://stefan.vanburen.xyz/" rel="alternate" type="text/html"/>
  <link href="https://stefan.vanburen.xyz/index.xml" rel="self" type="application/atom+xml" />
  <id>https://stefan.vanburen.xyz/</id>
  
  <updated>2026-06-02T09:11:22-04:00</updated>
  <rights>©️ 2026 Stefan VanBuren</rights>
  <icon>https://stefan.vanburen.xyz/favicon.svg</icon>
  <generator uri="https://gohugo.io/">Hugo</generator>
  <author>
    <name>Stefan VanBuren</name>
    <email>stefan@vanburen.xyz</email>
  </author>
  
  <entry>
    <title type="html"><![CDATA[Sargable]]></title>
    <link href="https://stefan.vanburen.xyz/til/sargable/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/sargable/</id>
    <published>2026-06-02T08:32:10-04:00</published>
    <updated>2026-06-02T09:11:22-04:00</updated>
    <content type="html"><![CDATA[<p>TIL of <a href="https://en.wikipedia.org/wiki/Sargable">&ldquo;sargable&rdquo;</a> conditions in SQL queries,
which just means that an index can be used to speed up the query.
One of those &ldquo;you already know the concept, but the <em>handle</em> is useful&rdquo; sort of things.</p>
]]></content>
    <source:markdown><![CDATA[
TIL of ["sargable"][1] conditions in SQL queries,
which just means that an index can be used to speed up the query.
One of those "you already know the concept, but the _handle_ is useful" sort of things.

[1]: https://en.wikipedia.org/wiki/Sargable
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Tangenting]]></title>
    <link href="https://stefan.vanburen.xyz/til/tangenting/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/tangenting/</id>
    <published>2026-04-26T20:03:30-04:00</published>
    <updated>2026-04-26T20:59:05-04:00</updated>
    <content type="html"><![CDATA[<p><a href="/til/tk">Again</a>,
not something I learned <em>today</em>,
but within the last few weeks:
&ldquo;tangenting&rdquo;.
I read about this in the wonderful Looking At Picture Books Substack,
<a href="https://lookingatpicturebooks.com/p/goodnight-moon">discussing</a> one of my (and Wilhelmina&rsquo;s) favorites, <cite><a href="https://en.wikipedia.org/wiki/Goodnight_Moon">Goodnight Moon</a></cite>:</p>
<blockquote>
<p>Tangenting is something you get yelled at for when you’re in art/design school. The theory is that the edges of things shouldn’t touch closely. You should instead look for large overlaps, so the different objects or elements are clearly divided — if they’re too close it feels badly planned, and harder to tell where one thing ends and the other begins, so they say.
Clement Hurd&hellip; was SUPER into tangenting.</p>
</blockquote>
<p>The whole thing is worth a read, if only for the tangenting discussion.</p>
]]></content>
    <source:markdown><![CDATA[
[Again](/til/tk),
not something I learned _today_,
but within the last few weeks:
"tangenting".
I read about this in the wonderful Looking At Picture Books Substack,
[discussing][1] one of my (and Wilhelmina's) favorites, <cite><a href="https://en.wikipedia.org/wiki/Goodnight_Moon">Goodnight Moon</a></cite>:

> Tangenting is something you get yelled at for when you’re in art/design school. The theory is that the edges of things shouldn’t touch closely. You should instead look for large overlaps, so the different objects or elements are clearly divided — if they’re too close it feels badly planned, and harder to tell where one thing ends and the other begins, so they say.
> Clement Hurd... was SUPER into tangenting.

The whole thing is worth a read, if only for the tangenting discussion.

[1]: https://lookingatpicturebooks.com/p/goodnight-moon
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[TK]]></title>
    <link href="https://stefan.vanburen.xyz/til/tk/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/tk/</id>
    <published>2026-04-25T19:43:17-04:00</published>
    <updated>2026-04-26T21:04:08-04:00</updated>
    <content type="html"><![CDATA[<p>TK means <a href="https://en.wikipedia.org/wiki/To_come_(publishing)">&ldquo;to come&rdquo;</a>.
You&rsquo;ll typically see it as &ldquo;TKTK&rdquo; or &ldquo;TKTKTK&rdquo;,
as a simple way to search (i.e., <kbd><a href="https://en.wikipedia.org/wiki/Command_key">⌘</a></kbd>+<kbd>f</kbd>) for details that haven&rsquo;t been written yet.
I use it when drafting blog posts to skip over a sentence or section I want to come back to write later,
to avoid losing momentum.</p>
<p>I didn&rsquo;t learn this today,
but within the last year or two (not too sure where!);
I was just reminded by seeing it in print<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> in <a href="https://en.wikipedia.org/wiki/Merrimack_Valley_Library_Consortium">my library</a>&rsquo;s edition of <a href="https://www.jezburrows.com/">Jez Burrow&rsquo;s</a> <a href="http://www.dictionarystories.com">Dictionary Stories</a>,
which is otherwise lovely so far.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>To be fair, I get it:
it&rsquo;s in a footnote where the &ldquo;TK&rdquo; is taking the place of a page number,
which I imagine is one of the last things to be solidified in a print book after all of the typesetting has taken place.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Funny timing, but a post on Daring Fireball the day after this one <a href="https://daringfireball.net/2026/04/nyt_wrong_crossword_grid">mentioned a similar snafu of printing &ldquo;Headline Goes Here&rdquo;</a> &ndash; &ldquo;TK&rdquo; may have been useful there!&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
    <source:markdown><![CDATA[
TK means ["to come"](https://en.wikipedia.org/wiki/To_come_(publishing)).
You'll typically see it as "TKTK" or "TKTKTK",
as a simple way to search (i.e., <kbd><a href="https://en.wikipedia.org/wiki/Command_key">⌘</a></kbd>+<kbd>f</kbd>) for details that haven't been written yet.
I use it when drafting blog posts to skip over a sentence or section I want to come back to write later,
to avoid losing momentum.

I didn't learn this today,
but within the last year or two (not too sure where!);
I was just reminded by seeing it in print[^1][^2] in [my library][3]'s edition of [Jez Burrow's][1] [Dictionary Stories][2],
which is otherwise lovely so far.

[^1]: To be fair, I get it:
it's in a footnote where the "TK" is taking the place of a page number,
which I imagine is one of the last things to be solidified in a print book after all of the typesetting has taken place.

[^2]: Funny timing, but a post on Daring Fireball the day after this one [mentioned a similar snafu of printing "Headline Goes Here"][4] -- "TK" may have been useful there!

[1]: https://www.jezburrows.com/
[2]: http://www.dictionarystories.com
[3]: https://en.wikipedia.org/wiki/Merrimack_Valley_Library_Consortium
[4]: https://daringfireball.net/2026/04/nyt_wrong_crossword_grid
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[<code>cells</code> - a language server for CEL]]></title>
    <link href="https://stefan.vanburen.xyz/blog/cells/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/cells/</id>
    <published>2026-02-22T19:24:55-05:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>This weekend, I built a <a href="https://microsoft.github.io/language-server-protocol/">language server</a> for <a href="https://cel.dev"><abbr title="Common Expression Language">CEL</abbr></a> called <a href="https://github.com/stefanvanburen/cells"><code>cells</code></a>.
It currently supports semantic highlighting, diagnostics, documentation on hover, signature help, references, document highlighting (for variable references), completion, and formatting.
<abbr title="Common Expression Language">CEL</abbr> is a pretty simple language,
so a language server for it doesn&rsquo;t really need to support things like go to definition or some of the other niceties that language servers typically support.
It only works on a single <code>.cel</code> file at a time,
which is a filetype currently supported only in neovim nightly but <a href="https://github.com/neovim/neovim/pull/37834">will be supported in neovim 0.12</a>.</p>
<p>Building this out, I brought over some of the ideas that I had integrated when I was building out some of the <a href="https://buf.build/docs/cli/editors-lsp/">Buf <abbr title="Language Server Protocol">LSP</abbr> server</a> at <code>$WORK</code>,
like that tests should have separate testdata files so that they could be independently verified by opening up an <abbr title="Language Server Protocol">LSP</abbr>-powered editor and taking a look at the files themselves.
On top of this, making the tests mostly table-based made it really simple for additional test cases to be added.</p>
<p>Of course, building this in a weekend took working with AI (using <a href="https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent"><code>pi</code></a> and <a href="https://ampcode.com/"><code>amp</code></a>).
One thing I had in mind was trying out the other <abbr title="Language Server Protocol">LSP</abbr> &ldquo;frameworks&rdquo; in the go ecosystem;
unfortunately, most seem to be pretty unmaintained these days.
<a href="https://github.com/go-language-server"><code>go.lsp.dev</code></a> progress has <a href="https://github.com/go-language-server/protocol/pull/52">largely stalled</a>,
and <a href="https://github.com/tliron/glsp">tliron/glsp</a> was the only other major contender,
which seemed to be in a similar state.</p>
<p>At the end of the day, the agent suggested that we ought to just borrow what charmbracelet had down with <a href="https://github.com/charmbracelet/x/tree/main/powernap/pkg/lsp/protocol"><code>powernap</code></a>,
and vendor in the <code>gopls</code> protocol code while maintaining the license,
and then &ldquo;write&rdquo; our own <a href="https://www.jsonrpc.org/specification"><abbr title="JSON Remote Procedure Call">JSONRPC</abbr></a> server,
based on the <a href="https://github.com/sourcegraph/jsonrpc2">sourcegraph implementation</a>,
but without the legacy cruft.
I may end up upstreaming some of this approach to <code>buf lsp</code>.</p>
<p>I&rsquo;m not sure how many other <abbr title="Language Server Protocol">LSP</abbr> features <code>cells</code> needs to be considered &ldquo;complete&rdquo;,
although I&rsquo;ll continue to track <abbr title="Common Expression Language">CEL</abbr> language features (as it&rsquo;s still in a pre-1.0 state as a language),
and I&rsquo;d like to add most of the <abbr title="Language Server Protocol">LSP</abbr> features as functionality in the <abbr title="Command-Line Interface">CLI</abbr>,
similar to <a href="https://go.dev/gopls/command-line"><code>gopls</code>&rsquo; <abbr title="Command-Line Interface">CLI</abbr> interface</a>.</p>
<p><code>cells</code> turned out to be a pretty perfect weekend project for working with agents:
<abbr title="Language Server Protocol">LSP</abbr> is <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/">fairly well-specified</a>,
with existing patterns,
a tight feedback loop,
and a great <a href="https://github.com/google/cel-go">reference library in <code>cel-go</code></a>.</p>
<p>Fun times to be building things. 👷‍♂️</p>
]]></content>
    <source:markdown><![CDATA[
This weekend, I built a [language server](https://microsoft.github.io/language-server-protocol/) for <a href="https://cel.dev"><abbr title="Common Expression Language">CEL</abbr></a> called [`cells`](https://github.com/stefanvanburen/cells).
It currently supports semantic highlighting, diagnostics, documentation on hover, signature help, references, document highlighting (for variable references), completion, and formatting.
<abbr title="Common Expression Language">CEL</abbr> is a pretty simple language,
so a language server for it doesn't really need to support things like go to definition or some of the other niceties that language servers typically support.
It only works on a single `.cel` file at a time,
which is a filetype currently supported only in neovim nightly but [will be supported in neovim 0.12](https://github.com/neovim/neovim/pull/37834).

Building this out, I brought over some of the ideas that I had integrated when I was building out some of the <a href="https://buf.build/docs/cli/editors-lsp/">Buf <abbr title="Language Server Protocol">LSP</abbr> server</a> at `$WORK`,
like that tests should have separate testdata files so that they could be independently verified by opening up an <abbr title="Language Server Protocol">LSP</abbr>-powered editor and taking a look at the files themselves.
On top of this, making the tests mostly table-based made it really simple for additional test cases to be added.

Of course, building this in a weekend took working with AI (using [`pi`](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent) and [`amp`](https://ampcode.com/)).
One thing I had in mind was trying out the other <abbr title="Language Server Protocol">LSP</abbr> "frameworks" in the go ecosystem;
unfortunately, most seem to be pretty unmaintained these days.
[`go.lsp.dev`](https://github.com/go-language-server) progress has [largely stalled](https://github.com/go-language-server/protocol/pull/52),
and [tliron/glsp](https://github.com/tliron/glsp) was the only other major contender,
which seemed to be in a similar state.

At the end of the day, the agent suggested that we ought to just borrow what charmbracelet had down with [`powernap`](https://github.com/charmbracelet/x/tree/main/powernap/pkg/lsp/protocol),
and vendor in the `gopls` protocol code while maintaining the license,
and then "write" our own <a href="https://www.jsonrpc.org/specification"><abbr title="JSON Remote Procedure Call">JSONRPC</abbr></a> server,
based on the [sourcegraph implementation](https://github.com/sourcegraph/jsonrpc2),
but without the legacy cruft.
I may end up upstreaming some of this approach to `buf lsp`.

I'm not sure how many other <abbr title="Language Server Protocol">LSP</abbr> features `cells` needs to be considered "complete",
although I'll continue to track <abbr title="Common Expression Language">CEL</abbr> language features (as it's still in a pre-1.0 state as a language),
and I'd like to add most of the <abbr title="Language Server Protocol">LSP</abbr> features as functionality in the <abbr title="Command-Line Interface">CLI</abbr>,
similar to <a href="https://go.dev/gopls/command-line"><code>gopls</code>' <abbr title="Command-Line Interface">CLI</abbr> interface</a>.

`cells` turned out to be a pretty perfect weekend project for working with agents:
<abbr title="Language Server Protocol">LSP</abbr> is [fairly well-specified](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/),
with existing patterns,
a tight feedback loop,
and a great [reference library in `cel-go`](https://github.com/google/cel-go).

Fun times to be building things. 👷‍♂️
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Neovim <code>:Inspect</code>]]></title>
    <link href="https://stefan.vanburen.xyz/til/neovim-inspect/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/neovim-inspect/</id>
    <published>2026-01-10T16:26:47-05:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>Yesterday, I was working on <a href="https://github.com/bufbuild/buf/pull/4270">improving <code>buf</code>&rsquo;s lsp protobuf semantic tokens</a>,
and needed to test out how my changes were working in a real <abbr title="Language Server Protocol">LSP</abbr> client.</p>
<p>I knew neovim had a couple of helper functions for inspecting the current position;
first I tried:</p>
<pre><code class="language-vim">:lua vim.print(vim.inspect_pos())
</code></pre>
<p>I figured there must be a function more specifically for semantic tokens:</p>
<pre><code class="language-vim">:lua vim.print(vim.lsp.semantic_tokens.get_at_pos())
</code></pre>
<p>But finally I landed on:</p>
<pre><code class="language-vim">:Inspect
</code></pre>
<p>(<a href="https://neovim.io/doc/user/lua.html#%3AInspect%21">neovim docs</a>)</p>
<p>which prints both treesitter and semantic tokens,
but in a much cleaner way than the two lua functions.</p>
<p>I&rsquo;ve <a href="https://github.com/stefanvanburen/dotfiles/commit/7dadb128015d37030f7b3b55b1e849b2873e499b">keymapped the underlying <code>vim.show_pos()</code> function to <code>&lt;leader&gt;i</code> in neovim</a> for easier access.</p>
]]></content>
    <source:markdown><![CDATA[
Yesterday, I was working on [improving `buf`'s lsp protobuf semantic tokens][1],
and needed to test out how my changes were working in a real <abbr title="Language Server Protocol">LSP</abbr> client.

I knew neovim had a couple of helper functions for inspecting the current position;
first I tried:

```vim
:lua vim.print(vim.inspect_pos())
```

I figured there must be a function more specifically for semantic tokens:

```vim
:lua vim.print(vim.lsp.semantic_tokens.get_at_pos())
```

But finally I landed on:

```vim
:Inspect
```

([neovim docs](https://neovim.io/doc/user/lua.html#%3AInspect%21))

which prints both treesitter and semantic tokens,
but in a much cleaner way than the two lua functions.

I've [keymapped the underlying `vim.show_pos()` function to `<leader>i` in neovim][2] for easier access.

[1]: https://github.com/bufbuild/buf/pull/4270
[2]: https://github.com/stefanvanburen/dotfiles/commit/7dadb128015d37030f7b3b55b1e849b2873e499b
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Fog of War]]></title>
    <link href="https://stefan.vanburen.xyz/blog/fog-of-war/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/fog-of-war/</id>
    <published>2025-11-01T19:45:36-04:00</published>
    <updated>2026-05-16T19:57:47-04:00</updated>
    <content type="html"><![CDATA[<p>Growing up playing World of Warcraft (WoW)<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, I became familiar with the concept of
<a href="https://breezewiki.com/wowpedia/wiki/Fog_of_War">&ldquo;Fog of War&rdquo;</a> — that is, areas of the world that your character hadn&rsquo;t visited.
Once you moved close enough to a certain area on the map, the fog would &ldquo;lift&rdquo;, and that
area on your map would be filled in moving forward.
Also, you&rsquo;d gain experience points for clearing Fog of War — at lower levels of the
game, you could level-up fairly quickly just by exploring new areas.</p>
<p>I&rsquo;ll give my brother credit — he was the first to see this as an opportunity for
exploration. He piloted his Troll Rogue, Zuljitan, on a journey across the continent of
Kalimdor after reaching level 10, the level at which Rogues in those days learned their
&ldquo;Sprint&rdquo; ability.
(We figured we&rsquo;d need this to escape the various mobs that would attack
your character from long distance, being tens of levels above our characters.
Rogues also could &ldquo;stealth&rdquo;, i.e. become invisible at the cost of movement speed, but
mobs at such a large level differential could see through stealth.)</p>
<p>Setting off south from the Crossroads in the Barrens, Zuljitan traipsed through the
Thousand Needles down to Tanaris to visit Gadgetzan, I believe he then made his way
through Un&rsquo;Goro Crater to Silithus, then over the mountains to Feralas before ultimately
dying for the umpteenth time in Desolace and hearthstone-ing back $HOME.</p>
<p>Although I originally played Horde (a Tauren Warrior), I eventually became drawn to the
Gnomes of Ironforge, and ended up on my own journey as the Gnome Rogue, Phread.
I liked that Gnomes had an advantage in the Engineering profession, and thought it was
neat that I could build goggles for my character at a low level, when most headgear in
the game was at that time for levels 40+ (of the original level-cap at 60).</p>
<p>The plan was hatched: this time, on Azeroth, I&rsquo;d need to make my way across the sea to
Gadgetzan to get the schematics I was looking for.
Phread&rsquo;s journey was a little easier: taking the Deeprun Tram from Ironforge to
Stormwind, he headed south through Elwynn Forest, Westfall and Duskwood down to the far
south of Stranglethorn Vale&rsquo;s Booty Bay, where he could catch the ship to Steamwheedle
Port and continue onwards to Gadgetzan.
I bought my schematic (I think?), and hearthstone&rsquo;d $HOME.</p>
<figure>
  <img
    src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Winslow_Homer_-_Homosassa_Jungle_%281904%29.jpg"
    alt="Homosassa Jungle by Winslow Homer"
    width="1024"
    height="661"
  />
  <figcaption>
    <a href="https://harvardartmuseums.org/collections/object/306835">
      Not quite Stranglethorn Vale
    </a>
  </figcaption>
</figure>
<hr>
<p>I&rsquo;m now about 20 years removed from my WoW-playing days (wow!), but I&rsquo;ve managed to find
a new way to clear Fog of War: <a href="https://citystrides.com">CityStrides</a>. CityStrides is an app that lets you
keep track of the streets that you&rsquo;ve run (or walked, or biked) in a city.
Once you&rsquo;ve run an entire street (by completing all the &ldquo;nodes&rdquo; along it), it&rsquo;s added to
your completed list.
Each city has a leaderboard of users ranked based on the percentage of streets they&rsquo;ve
completed.</p>
<p>I managed to make my way through 25% of Boston&rsquo;s ~4,400 streets before we moved to
Ipswich in late January.
Ipswich has only 356 streets at the time of writing.
(The smallest &ldquo;city&rdquo; I&rsquo;ve run, Sunfield, MI, has 18 streets — running 5 of them during a
5k over 10 years ago has resulted in me being the #1 (of 1) runner there on
CityStrides.) Before the move, I had nearly run 20% of Ipswich, mostly by running the
various roads out on Great Neck and Little Neck.
I made it my goal during paternity leave to finish as many streets in Ipswich as
possible (thanks to a loving, supportive wife and a relatively reasonably well-behaved
baby), hoping to possibly make it to 100% before my leave ended.</p>
<p>I&rsquo;ve been meaning to write this for a couple months since paternity leave ended, but
hadn&rsquo;t quite had the gumption to get this finished.
I haven&rsquo;t finished the 100% I was hoping to get to: instead, I&rsquo;ve been stuck at 80% for
most of the summer. The remaining streets are all 5+ miles away from my typical starting
point of our home, and are largely in areas that I&rsquo;d classify as unfriendly to running.
I&rsquo;m now slowly restarting my quest for 100% (VanBurens are box checkers, after all),
with an eye towards finishing by the end of the year.
I remain, for now, 2nd out of 506 on the <a href="https://citystrides.com/cities/2253">Ipswich CityStrides leaderboard</a>.</p>
<figure>
  <img
    src="https://iiif.micr.io/uIpfV/full/%5E830,/0/default.webp"
    alt="Tokubei running through a street by Kunimori"
    width="830"
    height="1152"
  />
  <figcaption>
    <a href="https://www.rijksmuseum.nl/en/collection/object/Tokubei-running-through-a-street--7e5f432ba724707931197c90284c5867?tab=data">
      Running the Streets
    </a>
  </figcaption>
</figure>
<p>Still, it&rsquo;s not about reaching 100%: it&rsquo;s about understanding the area in which you&rsquo;re
living at a deeper level, and looking closely at things (although, maybe not when cars
are screaming past you as you try to traipse down some of the unfriendly roads).
Clearing the Fog of War, if you will.</p>
<p>🌄</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>We used to drive our parents crazy on weekends &mdash;
we had a single shared WoW account,
so the first of us to wake up would scurry quietly downstairs to log on.
We each could play for an hour at a time before it was the others&rsquo; turn,
so when the other eventually woke up,
we&rsquo;d always say that we had &ldquo;just started playing 5 minutes ago&rdquo; to claim our full hour.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
    <source:markdown><![CDATA[
Growing up playing World of Warcraft (WoW)[^1], I became familiar with the concept of
["Fog of War"][1] — that is, areas of the world that your character hadn't visited.
Once you moved close enough to a certain area on the map, the fog would "lift", and that
area on your map would be filled in moving forward.
Also, you'd gain experience points for clearing Fog of War — at lower levels of the
game, you could level-up fairly quickly just by exploring new areas.

I'll give my brother credit — he was the first to see this as an opportunity for
exploration. He piloted his Troll Rogue, Zuljitan, on a journey across the continent of
Kalimdor after reaching level 10, the level at which Rogues in those days learned their
"Sprint" ability.
(We figured we'd need this to escape the various mobs that would attack
your character from long distance, being tens of levels above our characters.
Rogues also could "stealth", i.e. become invisible at the cost of movement speed, but
mobs at such a large level differential could see through stealth.)

Setting off south from the Crossroads in the Barrens, Zuljitan traipsed through the
Thousand Needles down to Tanaris to visit Gadgetzan, I believe he then made his way
through Un'Goro Crater to Silithus, then over the mountains to Feralas before ultimately
dying for the umpteenth time in Desolace and hearthstone-ing back $HOME.

Although I originally played Horde (a Tauren Warrior), I eventually became drawn to the
Gnomes of Ironforge, and ended up on my own journey as the Gnome Rogue, Phread.
I liked that Gnomes had an advantage in the Engineering profession, and thought it was
neat that I could build goggles for my character at a low level, when most headgear in
the game was at that time for levels 40+ (of the original level-cap at 60).

The plan was hatched: this time, on Azeroth, I'd need to make my way across the sea to
Gadgetzan to get the schematics I was looking for.
Phread's journey was a little easier: taking the Deeprun Tram from Ironforge to
Stormwind, he headed south through Elwynn Forest, Westfall and Duskwood down to the far
south of Stranglethorn Vale's Booty Bay, where he could catch the ship to Steamwheedle
Port and continue onwards to Gadgetzan.
I bought my schematic (I think?), and hearthstone'd $HOME.

<figure>
  <img
    src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Winslow_Homer_-_Homosassa_Jungle_%281904%29.jpg"
    alt="Homosassa Jungle by Winslow Homer"
    width="1024"
    height="661"
  />
  <figcaption>
    <a href="https://harvardartmuseums.org/collections/object/306835">
      Not quite Stranglethorn Vale
    </a>
  </figcaption>
</figure>

* * *

I'm now about 20 years removed from my WoW-playing days (wow!), but I've managed to find
a new way to clear Fog of War: [CityStrides][2]. CityStrides is an app that lets you
keep track of the streets that you've run (or walked, or biked) in a city.
Once you've run an entire street (by completing all the "nodes" along it), it's added to
your completed list.
Each city has a leaderboard of users ranked based on the percentage of streets they've
completed.

I managed to make my way through 25% of Boston's ~4,400 streets before we moved to
Ipswich in late January.
Ipswich has only 356 streets at the time of writing.
(The smallest "city" I've run, Sunfield, MI, has 18 streets — running 5 of them during a
5k over 10 years ago has resulted in me being the #1 (of 1) runner there on
CityStrides.) Before the move, I had nearly run 20% of Ipswich, mostly by running the
various roads out on Great Neck and Little Neck.
I made it my goal during paternity leave to finish as many streets in Ipswich as
possible (thanks to a loving, supportive wife and a relatively reasonably well-behaved
baby), hoping to possibly make it to 100% before my leave ended.

I've been meaning to write this for a couple months since paternity leave ended, but
hadn't quite had the gumption to get this finished.
I haven't finished the 100% I was hoping to get to: instead, I've been stuck at 80% for
most of the summer. The remaining streets are all 5+ miles away from my typical starting
point of our home, and are largely in areas that I'd classify as unfriendly to running.
I'm now slowly restarting my quest for 100% (VanBurens are box checkers, after all),
with an eye towards finishing by the end of the year.
I remain, for now, 2nd out of 506 on the [Ipswich CityStrides leaderboard][3].

<figure>
  <img
    src="https://iiif.micr.io/uIpfV/full/%5E830,/0/default.webp"
    alt="Tokubei running through a street by Kunimori"
    width="830"
    height="1152"
  />
  <figcaption>
    <a href="https://www.rijksmuseum.nl/en/collection/object/Tokubei-running-through-a-street--7e5f432ba724707931197c90284c5867?tab=data">
      Running the Streets
    </a>
  </figcaption>
</figure>

Still, it's not about reaching 100%: it's about understanding the area in which you're
living at a deeper level, and looking closely at things (although, maybe not when cars
are screaming past you as you try to traipse down some of the unfriendly roads).
Clearing the Fog of War, if you will.

🌄

[1]: https://breezewiki.com/wowpedia/wiki/Fog_of_War
[2]: https://citystrides.com
[3]: https://citystrides.com/cities/2253

[^1]: We used to drive our parents crazy on weekends ---
we had a single shared WoW account,
so the first of us to wake up would scurry quietly downstairs to log on.
We each could play for an hour at a time before it was the others' turn,
so when the other eventually woke up,
we'd always say that we had "just started playing 5 minutes ago" to claim our full hour.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Neovim <code>:InspectTree</code> Tree-sitter]]></title>
    <link href="https://stefan.vanburen.xyz/til/nvim-inspect-tree-sitter/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/nvim-inspect-tree-sitter/</id>
    <published>2025-09-10T19:50:36-04:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>I had assumed that to interact with Tree-sitter in Neovim,
you still needed to use the utilities from <a href="https://github.com/nvim-treesitter/nvim-treesitter">nvim-treesitter</a>.
Apparently that&rsquo;s not the case &mdash;
Neovim has had a builtin <a href="https://neovim.io/doc/user/treesitter.html#%3AInspectTree"><code>:InspectTree</code></a> command that brings up the <abbr title="Abstract Syntax Tree">AST</abbr> from Tree-sitter in a split window,
which is <a href="https://neovim.io/doc/user/options.html#'scrollbind'"><code>scrollbound</code></a> to the original window and highlights the selected node in both windows.</p>
<p>It also has a <a href="https://gpanders.com/blog/whats-new-in-neovim-0.10/#tree-sitter-query-editor">query editor</a>,
which I haven&rsquo;t yet had the need for,
but it&rsquo;s good to know it exists.</p>
]]></content>
    <source:markdown><![CDATA[
I had assumed that to interact with Tree-sitter in Neovim,
you still needed to use the utilities from [nvim-treesitter][1].
Apparently that's not the case ---
Neovim has had a builtin [`:InspectTree`][2] command that brings up the <abbr title="Abstract Syntax Tree">AST</abbr> from Tree-sitter in a split window,
which is [`scrollbound`][3] to the original window and highlights the selected node in both windows.

It also has a [query editor][4],
which I haven't yet had the need for,
but it's good to know it exists.

[1]: https://github.com/nvim-treesitter/nvim-treesitter
[2]: https://neovim.io/doc/user/treesitter.html#%3AInspectTree
[3]: https://neovim.io/doc/user/options.html#'scrollbind'
[4]: https://gpanders.com/blog/whats-new-in-neovim-0.10/#tree-sitter-query-editor
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[WikiGnome]]></title>
    <link href="https://stefan.vanburen.xyz/til/wiki-gnome/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/wiki-gnome/</id>
    <published>2025-09-02T09:10:30-04:00</published>
    <updated>2026-04-16T19:45:21-04:00</updated>
    <content type="html"><![CDATA[<p>TIL about <a href="https://en.m.wikipedia.org/wiki/Wikipedia:WikiGnome">WikiGnomes</a>:</p>
<blockquote>
<p>A WikiGnome is a wiki user who makes useful incremental edits without clamoring for attention.
WikiGnomes work behind the scenes of a wiki, tying up little loose ends and making things run more smoothly.</p>
</blockquote>
<p>Some of the most valuable work can be the tiny changes that leave things in a better place than they were found.</p>
<p>Also, sounds a little like <a href="/blog/prutsen">prutsen</a>.</p>
]]></content>
    <source:markdown><![CDATA[
TIL about [WikiGnomes][1]:

> A WikiGnome is a wiki user who makes useful incremental edits without clamoring for attention.
> WikiGnomes work behind the scenes of a wiki, tying up little loose ends and making things run more smoothly.

Some of the most valuable work can be the tiny changes that leave things in a better place than they were found.

Also, sounds a little like [prutsen](/blog/prutsen).

[1]: https://en.m.wikipedia.org/wiki/Wikipedia:WikiGnome
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Small Seasons]]></title>
    <link href="https://stefan.vanburen.xyz/blog/small-seasons/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/small-seasons/</id>
    <published>2025-08-23T14:52:06-04:00</published>
    <updated>2026-05-16T19:57:47-04:00</updated>
    <content type="html"><![CDATA[<p>Since moving out to suburban Ipswich from Boston,
I’ve been on a “seasons” kick —
you know, those things that feel wonderful the first couple weeks they’re here and then eventually drag on until the next one comes along?
(Currently waiting patiently for Autumn to get started in New England!)</p>
<figure>
  <img
    src="https://nitter.net/pic/orig/media%2FFF-E7sJXoAwdFDz.jpg"
    alt="Pondering my seasons"
    width="1600"
    height="1200"
  />
  <figcaption>
    <a href="https://nitter.net/simonsarris/status/1468038376280530947">
      Pondering my seasons
    </a>
  </figcaption>
</figure>
<p>While I’m more thoroughly immersed in the seasons now than when I was in the city,
I still spend a considerable chunk of time computing.
Computing isn’t seasonal, unless you make it so.</p>
<p>Since I came across <a href="https://rosszurowski.com/">Ross Zurowski</a>’s <a href="https://smallseasons.guide/">smallseasons.guide</a> (which details the Japanese concept of <em>sekki</em>, or two-week-ish “seasons” — Ross goes into detail on this and other ponderings in <a href="https://rosszurowski.com/log/2018/small-seasons-long-calendars">“On Small Seasons and Long Calendars”</a>), I’ve had the <a href="https://gist.github.com/rosszurowski/c7132bf37f7344a775e262619f97ff18">iCal</a> humming along in the background, eager for a chance to put it to use.</p>
<p>We’ve had our first hint of Autumn weather this last week, and I saw my opportunity.
Fall supposedly starts on September 21st around the equinox
(and then lasts until December 21st 🙄),
but everyone knows this is a hoax.
Autumn actually starts much earlier:
August 8th, based on the <em>sekki</em>.
I <a href="https://github.com/stefanvanburen/dotfiles/commit/e4ff21cde49d887e9a44d2d75118bae0742bd7ce">configured neovim to change between echanovski’s <code>mini{spring,summer,autumn,winter}</code> colorschemes based on the change of the small seasons</a>,
and similarly updated my site’s <a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz/tree/d20c5ab7590d6e5e8cd2717e4cd1a1a27e056b82/item/layouts/_partials/header.html#L12-50"><code>&lt;h1&gt;</code></a>s
and <a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz/tree/d20c5ab7590d6e5e8cd2717e4cd1a1a27e056b82/item/assets/favicon-template.svg">favicon</a> to follow suit.</p>
<p>Just a little thing to keep me grounded in the seasons,
despite the indoor setting,
along with our morning stroller walks!</p>
<p>🍂</p>
<hr>
<p>P.S.: Another way I&rsquo;ve found to mind the seasons is to update my <a href="https://pixfabrik.com/livingworlds/">Living Worlds app</a> on the first day of each month,
as a reminder of the passing time.</p>
]]></content>
    <source:markdown><![CDATA[
Since moving out to suburban Ipswich from Boston,
I’ve been on a “seasons” kick —
you know, those things that feel wonderful the first couple weeks they’re here and then eventually drag on until the next one comes along?
(Currently waiting patiently for Autumn to get started in New England!)

<figure>
  <img
    src="https://nitter.net/pic/orig/media%2FFF-E7sJXoAwdFDz.jpg"
    alt="Pondering my seasons"
    width="1600"
    height="1200"
  />
  <figcaption>
    <a href="https://nitter.net/simonsarris/status/1468038376280530947">
      Pondering my seasons
    </a>
  </figcaption>
</figure>

While I’m more thoroughly immersed in the seasons now than when I was in the city,
I still spend a considerable chunk of time computing.
Computing isn’t seasonal, unless you make it so.

Since I came across [Ross Zurowski](https://rosszurowski.com/)’s [smallseasons.guide](https://smallseasons.guide/) (which details the Japanese concept of *sekki*, or two-week-ish “seasons” — Ross goes into detail on this and other ponderings in [“On Small Seasons and Long Calendars”](https://rosszurowski.com/log/2018/small-seasons-long-calendars)), I’ve had the [iCal](https://gist.github.com/rosszurowski/c7132bf37f7344a775e262619f97ff18) humming along in the background, eager for a chance to put it to use.

We’ve had our first hint of Autumn weather this last week, and I saw my opportunity.
Fall supposedly starts on September 21st around the equinox
(and then lasts until December 21st 🙄),
but everyone knows this is a hoax.
Autumn actually starts much earlier:
August 8th, based on the *sekki*.
I [configured neovim to change between echanovski’s `mini{spring,summer,autumn,winter}` colorschemes based on the change of the small seasons](https://github.com/stefanvanburen/dotfiles/commit/e4ff21cde49d887e9a44d2d75118bae0742bd7ce),
and similarly updated my site’s [`<h1>`](https://git.sr.ht/~svbn/stefan.vanburen.xyz/tree/d20c5ab7590d6e5e8cd2717e4cd1a1a27e056b82/item/layouts/_partials/header.html#L12-50)s
and [favicon](https://git.sr.ht/~svbn/stefan.vanburen.xyz/tree/d20c5ab7590d6e5e8cd2717e4cd1a1a27e056b82/item/assets/favicon-template.svg) to follow suit.

Just a little thing to keep me grounded in the seasons,
despite the indoor setting,
along with our morning stroller walks!

🍂

* * *

P.S.: Another way I've found to mind the seasons is to update my [Living Worlds app][1] on the first day of each month,
as a reminder of the passing time.

[1]: https://pixfabrik.com/livingworlds/
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Wrapping arrays in <code>jq</code>]]></title>
    <link href="https://stefan.vanburen.xyz/til/jq-wrap-array/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/jq-wrap-array/</id>
    <published>2025-08-20T08:55:08-04:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>Riffing on <a href="https://macwright.com/2025/08/19/using-super">Tom MacWright&rsquo;s &ldquo;Using super&rdquo; post</a> from yesterday,
I saw the post and immediately thought of using <a href="https://jqlang.org"><code>jq</code></a>.</p>
<p>I thought I might get what he was looking for with my initial attempt, something like:</p>
<pre><code class="language-console">$ pbpaste | jq '.[].name'
&quot;ALGOLIA_API_KEY&quot;
&quot;AMAZON_AWS_ACCESS_KEY_ID&quot;
</code></pre>
<p>&hellip; but that left out the wrapper array.
I figured I might need some other <code>jq</code> function or pipe,
but instead I found that you can just wrap the entire expression in square brackets to wrap the result:</p>
<pre><code class="language-console">$ pbpaste | jq '[.[].name]'
&quot;ALGOLIA_API_KEY&quot;
&quot;AMAZON_AWS_ACCESS_KEY_ID&quot;
</code></pre>
<p>Regardless, <code>jq</code> feels worth the investment,
especially given <a href="https://github.com/fiatjaf/awesome-jq">the community (and alternative implementations)</a> built up around it.</p>
]]></content>
    <source:markdown><![CDATA[
Riffing on [Tom MacWright's "Using super" post][1] from yesterday,
I saw the post and immediately thought of using [`jq`][2].

I thought I might get what he was looking for with my initial attempt, something like:

```console
$ pbpaste | jq '.[].name'
"ALGOLIA_API_KEY"
"AMAZON_AWS_ACCESS_KEY_ID"
```

... but that left out the wrapper array.
I figured I might need some other `jq` function or pipe,
but instead I found that you can just wrap the entire expression in square brackets to wrap the result:

```console
$ pbpaste | jq '[.[].name]'
"ALGOLIA_API_KEY"
"AMAZON_AWS_ACCESS_KEY_ID"
```

Regardless, `jq` feels worth the investment,
especially given [the community (and alternative implementations)][3] built up around it.

[1]: https://macwright.com/2025/08/19/using-super
[2]: https://jqlang.org
[3]: https://github.com/fiatjaf/awesome-jq
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Internet Phonebook]]></title>
    <link href="https://stefan.vanburen.xyz/blog/internet-phonebook/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/internet-phonebook/</id>
    <published>2025-07-07T18:16:22-04:00</published>
    <updated>2025-07-07T18:17:31-04:00</updated>
    <content type="html"><![CDATA[<p>I was lucky enough to be included in the inaugural <a href="https://internetphonebook.net">Internet Phonebook</a>, in the “Atmosphere” section on p. 22 — I’m number #58 if you’d like to <a href="https://internetphonebook.net/#dial-a-site">Dial-my-site</a>.</p>
<p>I ordered a copy to peruse for myself, and lo and behold, I had forgotten that I had submitted on a whim. Probably the first and last time a link to this little site of mine will appear in print. Still, glad to be included in, and support, this grand directory of the poetic web.</p>
]]></content>
    <source:markdown><![CDATA[
I was lucky enough to be included in the inaugural [Internet Phonebook](https://internetphonebook.net), in the “Atmosphere” section on p. 22 — I’m number #58 if you’d like to [Dial-my-site](https://internetphonebook.net/#dial-a-site).

I ordered a copy to peruse for myself, and lo and behold, I had forgotten that I had submitted on a whim. Probably the first and last time a link to this little site of mine will appear in print. Still, glad to be included in, and support, this grand directory of the poetic web.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Neovim LSP Defaults]]></title>
    <link href="https://stefan.vanburen.xyz/til/neovim-lsp-defaults/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/neovim-lsp-defaults/</id>
    <published>2025-07-01T16:33:55-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>neovim released v0.11.0 a little while back,
and in the interest of using the defaults,
I <a href="https://github.com/stefanvanburen/dotfiles/commit/8f8ad882a1092c8c032b13e118efc960317759f4#diff-568ff9545a264b9c3d0daec0395b05bf4217c6448328e6f2e70b1ad55adcbd4eL533-R534">removed some of my <abbr title="Language Server Protocol">LSP</abbr>-related keymaps</a> that are now <a href="https://gpanders.com/blog/whats-new-in-neovim-0-11/#defaults">automatically mapped</a>.
These are listed at <a href="https://neovim.io/doc/user/lsp.html#_defaults"><code>:help lsp-defaults</code></a>.</p>
]]></content>
    <source:markdown><![CDATA[
neovim released v0.11.0 a little while back,
and in the interest of using the defaults,
I [removed some of my <abbr title="Language Server Protocol">LSP</abbr>-related keymaps][1] that are now [automatically mapped][2].
These are listed at [`:help lsp-defaults`][3].

[1]: https://github.com/stefanvanburen/dotfiles/commit/8f8ad882a1092c8c032b13e118efc960317759f4#diff-568ff9545a264b9c3d0daec0395b05bf4217c6448328e6f2e70b1ad55adcbd4eL533-R534
[2]: https://gpanders.com/blog/whats-new-in-neovim-0-11/#defaults
[3]: https://neovim.io/doc/user/lsp.html#_defaults
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Postgres <code>citext</code> type]]></title>
    <link href="https://stefan.vanburen.xyz/til/postgres-citext/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/postgres-citext/</id>
    <published>2024-08-21T07:49:04-04:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>TIL about the Postgres <code>citext</code> type (that is, <a href="https://www.postgresql.org/docs/current/citext.html">case-insensitive text type</a>),
from reading <a href="https://github.com/surprisetalk/ding/blob/main/db.sql#L18-L19">this schema</a>.</p>
<p>Based on this tip from the Postgres docs,
this might not be the right type for general text:</p>
<blockquote>
<p>Consider using nondeterministic collations (see <a href="https://www.postgresql.org/docs/current/collation.html#COLLATION-NONDETERMINISTIC">Section 24.2.2.4</a>) instead of this module.
They can be used for case-insensitive comparisons,
accent-insensitive comparisons,
and other combinations,
and they handle more Unicode special cases correctly.</p>
</blockquote>
<p>&hellip; but if you&rsquo;re storing something like a username or email address that&rsquo;s already limited to an ascii-ish character set,
and want to keep things unique on a case-insensitive basis,
it might be what you&rsquo;re looking for.</p>
]]></content>
    <source:markdown><![CDATA[
TIL about the Postgres `citext` type (that is, [case-insensitive text type][1]),
from reading [this schema][2].

Based on this tip from the Postgres docs,
this might not be the right type for general text:

> Consider using nondeterministic collations (see [Section 24.2.2.4][3]) instead of this module.
> They can be used for case-insensitive comparisons,
> accent-insensitive comparisons,
> and other combinations,
> and they handle more Unicode special cases correctly.

... but if you're storing something like a username or email address that's already limited to an ascii-ish character set,
and want to keep things unique on a case-insensitive basis,
it might be what you're looking for.

[1]: https://www.postgresql.org/docs/current/citext.html
[2]: https://github.com/surprisetalk/ding/blob/main/db.sql#L18-L19
[3]: https://www.postgresql.org/docs/current/collation.html#COLLATION-NONDETERMINISTIC
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Go Pass by Value or Pointer]]></title>
    <link href="https://stefan.vanburen.xyz/til/go-passing/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/go-passing/</id>
    <published>2024-08-18T19:09:03-04:00</published>
    <updated>2024-08-18T19:14:46-04:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;ve long struggled with the choice of how to pass around Go values,
specifically structs.
I was happy to find <a href="https://blog.percywegmann.com/2023/09/20/go-pass-by-value-or-pointer.html">this article</a> on the topic,
specifically the conclusion,
reproduced here:</p>
<blockquote>
<ol>
<li>Types that are not structs or arrays should be passed by value.</li>
<li>Struct types that don’t export their members and are clearly built as immutable value types, like time.Time, should be passed by value. Note that these types are relatively rare, and are even rarer to be defined by you.</li>
<li>Arrays and all other struct types should be passed by pointer, whether small, large, stateful, or whatever.</li>
<li>If you’re passing data that could be mutated by a concurrent process and its important to you for that data not to be mutated, explicitly make a copy of it before passing it along. Be aware that you can’t just rely on passing the data by value since that does not create a deep copy.</li>
</ol>
</blockquote>
<p>I&rsquo;ll be referencing this moving forward,
and taking the guesswork out of my passing conventions.</p>
]]></content>
    <source:markdown><![CDATA[
I've long struggled with the choice of how to pass around Go values,
specifically structs.
I was happy to find [this article][1] on the topic,
specifically the conclusion,
reproduced here:

> 1. Types that are not structs or arrays should be passed by value.
> 1. Struct types that don’t export their members and are clearly built as immutable value types, like time.Time, should be passed by value. Note that these types are relatively rare, and are even rarer to be defined by you.
> 1. Arrays and all other struct types should be passed by pointer, whether small, large, stateful, or whatever.
> 1. If you’re passing data that could be mutated by a concurrent process and its important to you for that data not to be mutated, explicitly make a copy of it before passing it along. Be aware that you can’t just rely on passing the data by value since that does not create a deep copy.

I'll be referencing this moving forward,
and taking the guesswork out of my passing conventions.

[1]: https://blog.percywegmann.com/2023/09/20/go-pass-by-value-or-pointer.html
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[HTML datetime duration]]></title>
    <link href="https://stefan.vanburen.xyz/til/html-date-duration/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/html-date-duration/</id>
    <published>2024-07-13T10:18:24-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Last week I learned that an <abbr title="HyperText Markup Language">HTML</abbr> <code>datetime</code> attribute can <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time#valid_datetime_values">represent a duration</a>.</p>
<p>For example:</p>
<pre><code class="language-html">&lt;time datetime=&quot;PT4H18M3S&quot;&gt;
  4 hours, 18 minutes and 3 seconds
&lt;/time&gt;
</code></pre>
<p>The syntax is an <a href="https://en.wikipedia.org/wiki/ISO_8601#Durations"><abbr title="International Organization for Standardization">ISO</abbr> 8601 Duration</a>.</p>
]]></content>
    <source:markdown><![CDATA[
Last week I learned that an <abbr title="HyperText Markup Language">HTML</abbr> `datetime` attribute can [represent a duration][valid-datetime-values].

For example:

```html
<time datetime="PT4H18M3S">
  4 hours, 18 minutes and 3 seconds
</time>
```

The syntax is an <a href="https://en.wikipedia.org/wiki/ISO_8601#Durations"><abbr title="International Organization for Standardization">ISO</abbr> 8601 Duration</a>.

[valid-datetime-values]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time#valid_datetime_values
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[GBrowse to another branch with Fugitive.vim]]></title>
    <link href="https://stefan.vanburen.xyz/til/fugitive-objects/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/fugitive-objects/</id>
    <published>2024-07-11T08:28:24-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Last week, I watched a coworker bring up a file on a different branch using fugitive.vim&rsquo;s <a href="https://github.com/tpope/vim-fugitive/blob/8c8cdf4405cb8bdb70dd9812a33bb52363a87dbc/doc/fugitive.txt#L254">:GBrowse</a> and I was floored:
almost daily I find myself using it to open the <abbr title="Uniform Resource Locator">URL</abbr> to a file to share (or a few lines).</p>
<p>However, much of the time I&rsquo;m working on a different branch than the one I want to share,
and navigating to the same file on a different branch in the GitHub <abbr title="User Interface">UI</abbr> takes too many steps.
First, you realize you&rsquo;re on the wrong branch,
then you change branches,
then you have to navigate back to the file again.</p>
<p>I had always been vaguely aware of <a href="https://github.com/tpope/vim-fugitive/blob/8c8cdf4405cb8bdb70dd9812a33bb52363a87dbc/doc/fugitive.txt#L611"><code>:h fugitive-object</code></a>,
but had never put them to use.</p>
<p>The object you&rsquo;re looking for is <code>&lt;branch&gt;:%</code>, meaning:
bring up this file (<code>%</code>) on branch <code>&lt;branch&gt;</code>.</p>
<p>For example:</p>
<pre><code class="language-vim">:GBrowse main:%
</code></pre>
<p>And of course, this just works with all of the <a href="https://github.com/tpope/vim-fugitive/blob/8c8cdf4405cb8bdb70dd9812a33bb52363a87dbc/doc/fugitive.txt#L264-L277"><code>:GBrowse</code> variants</a>.</p>
]]></content>
    <source:markdown><![CDATA[
Last week, I watched a coworker bring up a file on a different branch using fugitive.vim's [:GBrowse][GBrowse] and I was floored:
almost daily I find myself using it to open the <abbr title="Uniform Resource Locator">URL</abbr> to a file to share (or a few lines).

However, much of the time I'm working on a different branch than the one I want to share,
and navigating to the same file on a different branch in the GitHub <abbr title="User Interface">UI</abbr> takes too many steps.
First, you realize you're on the wrong branch,
then you change branches,
then you have to navigate back to the file again.

I had always been vaguely aware of [`:h fugitive-object`][fugitive-object],
but had never put them to use.

The object you're looking for is `<branch>:%`, meaning:
bring up this file (`%`) on branch `<branch>`.

For example:

```vim
:GBrowse main:%
```

And of course, this just works with all of the [`:GBrowse` variants][GBrowse-variants].

[GBrowse]: https://github.com/tpope/vim-fugitive/blob/8c8cdf4405cb8bdb70dd9812a33bb52363a87dbc/doc/fugitive.txt#L254
[fugitive-object]: https://github.com/tpope/vim-fugitive/blob/8c8cdf4405cb8bdb70dd9812a33bb52363a87dbc/doc/fugitive.txt#L611
[GBrowse-variants]: https://github.com/tpope/vim-fugitive/blob/8c8cdf4405cb8bdb70dd9812a33bb52363a87dbc/doc/fugitive.txt#L264-L277
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Neovim nfnl Directory Configuration]]></title>
    <link href="https://stefan.vanburen.xyz/til/nvim-nfnl-directory-configuration/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/nvim-nfnl-directory-configuration/</id>
    <published>2024-05-31T08:16:19-04:00</published>
    <updated>2024-06-06T13:49:42-04:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;ve long wanted to figure out how to get <a href="https://github.com/olical/nfnl?tab=readme-ov-file#directory-local-neovim-configuration-in-fennel"><code>nfnl's</code> directory-local configuration</a> working,
but it took me until yesterday to figure it out &mdash;
mostly by <a href="https://github.com/Olical/nfnl/discussions/41">opening a discussion</a> and then realizing my mistakes. 🤡</p>
<p>First, <code>exrc</code> needs to be <a href="https://github.com/stefanvanburen/dotfiles/commit/beed66f9a0a872f2a0db07b0e2de36ad262e4649">enabled</a>.
This will ultimately load the contents of the generated <code>.nvim.lua</code> file,
as described in <a href="https://neovim.io/doc/user/options.html#'exrc'"><code>:help 'exrc'</code></a></p>
<p>Next, create an <code>.nfnl.fnl</code> file in the directory.
I&rsquo;ve been creating mine as:</p>
<pre><code class="language-fennel">;; .nfnl.fnl
{:source-file-patterns [:.nvim.fnl]}
</code></pre>
<p>&hellip; which means that <code>nfnl</code> only needs to compile the <code>.nvim.fnl</code> file into <code>.nvim.lua</code>.</p>
<p>From there, I create a <code>.nvim.fnl</code> file with my configuration.
Often this is to set up a particular project-local formatter or linter with conform or nvim-lint,
so it may look something like:</p>
<pre><code class="language-fennel">;; .nvim.fnl
(let [conform (require :conform)]
  (set conform.formatters_by_ft.json [:prettier]))
</code></pre>
<p>With this I&rsquo;ve been able to <a href="https://github.com/stefanvanburen/dotfiles/commit/b7ab7019510c4554e5eb432996bb8378d9e9bd44">remove a few $WORK specific formatters</a>.</p>
<p>Lastly, I&rsquo;ve <a href="https://github.com/stefanvanburen/dotfiles/commit/1d740e2a0337678eb6084e4c97cd0a096ea46a71">globally gitignored</a> these files so they aren&rsquo;t accidentally committed.
It&rsquo;s possible to commit the <code>.nvim.lua</code> files to share with other neovim users that have <code>exrc</code> enabled,
but I doubt I&rsquo;ll use that very often &mdash;
I&rsquo;m still undecided on if I&rsquo;ll commit these for my personal projects.</p>
<p>📁</p>
]]></content>
    <source:markdown><![CDATA[
I've long wanted to figure out how to get [`nfnl's` directory-local configuration][1] working,
but it took me until yesterday to figure it out ---
mostly by [opening a discussion][2] and then realizing my mistakes. 🤡

First, `exrc` needs to be [enabled][3].
This will ultimately load the contents of the generated `.nvim.lua` file,
as described in [`:help 'exrc'`][4]

Next, create an `.nfnl.fnl` file in the directory.
I've been creating mine as:

```fennel
;; .nfnl.fnl
{:source-file-patterns [:.nvim.fnl]}
```

... which means that `nfnl` only needs to compile the `.nvim.fnl` file into `.nvim.lua`.

From there, I create a `.nvim.fnl` file with my configuration.
Often this is to set up a particular project-local formatter or linter with conform or nvim-lint,
so it may look something like:

```fennel
;; .nvim.fnl
(let [conform (require :conform)]
  (set conform.formatters_by_ft.json [:prettier]))
```

With this I've been able to [remove a few $WORK specific formatters][6].

Lastly, I've [globally gitignored][5] these files so they aren't accidentally committed.
It's possible to commit the `.nvim.lua` files to share with other neovim users that have `exrc` enabled,
but I doubt I'll use that very often ---
I'm still undecided on if I'll commit these for my personal projects.

📁

[1]: https://github.com/olical/nfnl?tab=readme-ov-file#directory-local-neovim-configuration-in-fennel
[2]: https://github.com/Olical/nfnl/discussions/41
[3]: https://github.com/stefanvanburen/dotfiles/commit/beed66f9a0a872f2a0db07b0e2de36ad262e4649
[4]: https://neovim.io/doc/user/options.html#'exrc'
[5]: https://github.com/stefanvanburen/dotfiles/commit/1d740e2a0337678eb6084e4c97cd0a096ea46a71
[6]: https://github.com/stefanvanburen/dotfiles/commit/b7ab7019510c4554e5eb432996bb8378d9e9bd44
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Git Stash File(s)]]></title>
    <link href="https://stefan.vanburen.xyz/til/git-stash-file/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/git-stash-file/</id>
    <published>2024-05-24T07:39:46-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>This isn&rsquo;t something I&rsquo;m just learning today,
but I&rsquo;ve looked this up enough to warrant writing it down.</p>
<p>I&rsquo;m often working on some code,
and come across something unrelated that I want to change.
So I do &mdash; but it really doesn&rsquo;t belong in this commit or <abbr title="Pull Request">PR</abbr>/patch/etc.!</p>
<p>(I&rsquo;ve yet to put in the reps to understand <a href="https://git-scm.com/docs/git-worktree">worktrees</a> or <a href="https://martinvonz.github.io/jj">jujutsu</a>,
which I&rsquo;m assuming are the &ldquo;real&rdquo; answers here.)</p>
<p>For now, you can provide <code>git stash push</code> a filename (or multiple!) to come back to those changes later:</p>
<pre><code class="language-console">$ git stash push &lt;filename(s)&gt;
</code></pre>
<p>I&rsquo;m sure there&rsquo;s also a way to do this incrementally with <code>-p</code> or <a href="https://github.com/tpope/vim-fugitive">Fugitive&rsquo;s</a> interface.</p>
<p>📦</p>
]]></content>
    <source:markdown><![CDATA[
This isn't something I'm just learning today,
but I've looked this up enough to warrant writing it down.

I'm often working on some code,
and come across something unrelated that I want to change.
So I do --- but it really doesn't belong in this commit or <abbr title="Pull Request">PR</abbr>/patch/etc.!

(I've yet to put in the reps to understand [worktrees][] or [jujutsu][],
which I'm assuming are the "real" answers here.)

For now, you can provide `git stash push` a filename (or multiple!) to come back to those changes later:

```console
$ git stash push <filename(s)>
```

I'm sure there's also a way to do this incrementally with `-p` or [Fugitive's][fugitive] interface.

📦

[worktrees]: https://git-scm.com/docs/git-worktree
[jujutsu]: https://martinvonz.github.io/jj
[fugitive]: https://github.com/tpope/vim-fugitive
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Smart Speed]]></title>
    <link href="https://stefan.vanburen.xyz/blog/smart-speed/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/smart-speed/</id>
    <published>2023-12-30T18:00:27-05:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>My <a href="https://overcast.fm">Overcast</a> settings screen shows the following:</p>
<blockquote>
<p>Smart Speed has saved you an extra 949 hours beyond speed adjustments alone.</p>
</blockquote>
<p>That&rsquo;s a lot of time. I&rsquo;ve been using Overcast to listen to podcasts since it launched, with years now of what-I&rsquo;d-call &ldquo;heavy&rdquo; podcast listening.
I&rsquo;ve spent most of that time listening on 1.5 or 2x speed,
with smart speed always turned on (after all, who can resist number-go-up).</p>
<p>I recently read Keenan&rsquo;s <a href="https://gkeenan.co/avgb/hot-take-its-okay-if-we-dont-consume-all-of-the-worlds-information-before-we-die">&ldquo;Hot Take: It&rsquo;s okay if we don&rsquo;t consume all of the world&rsquo;s information before we die&rdquo;</a>, and then subsequently <em>also</em> listened to the audioblog version.
While listening, I turned off Smart Speed, and it really struck me the difference that I&rsquo;d been missing all this time.
It&rsquo;s not as though I didn&rsquo;t intuitively know that the audio was sped up,
but there&rsquo;s something different about listening to people talking to each other without pauses.</p>
<p>For one, you might assume that the speakers are more intelligent:
they never need to consider their responses,
as they&rsquo;re always immediately responding.
In a similar vein, there&rsquo;s no context clues as to what one person said that made others on an episode <em>take time</em> to consider -
which might be important than something that can be responded to immediately.
And of course, as Keenan mentions in his post,
for the producers that <em>do</em> edit their audio,
using Smart Speed removes the potential to appreciate the pauses as they occur,
whether they&rsquo;re within conversation or somewhere else.</p>
<p>Back to Keenan&rsquo;s audioblog, you can clearly hear this listening to the end of the podcast as he slows down for emphasis towards the end.</p>
<p>All of this is much better articulated by Simon Sarris in his <a href="https://map.simonsarris.com/p/audiobooks-are-books-and-theyre-also">thoughts on audiobooks</a>, where he notes:</p>
<blockquote>
<p>Listening is a skill &hellip; Generally the pacing of nearly all media has quickened. Possibly the delivery of “more, faster” is the result of a too-great respect for novelty as an artistic flourish.</p>
</blockquote>
<p>Clearly, none of this is groundbreaking -
but reconsidering using Smart Speed has been a nice change of pace.</p>
<p>🐢</p>
]]></content>
    <source:markdown><![CDATA[
My [Overcast][overcast] settings screen shows the following:

> Smart Speed has saved you an extra 949 hours beyond speed adjustments alone.

That's a lot of time. I've been using Overcast to listen to podcasts since it launched, with years now of what-I'd-call "heavy" podcast listening.
I've spent most of that time listening on 1.5 or 2x speed,
with smart speed always turned on (after all, who can resist number-go-up).

I recently read Keenan's ["Hot Take: It's okay if we don't consume all of the world's information before we die"][keenan-hot-take], and then subsequently _also_ listened to the audioblog version.
While listening, I turned off Smart Speed, and it really struck me the difference that I'd been missing all this time.
It's not as though I didn't intuitively know that the audio was sped up,
but there's something different about listening to people talking to each other without pauses.

For one, you might assume that the speakers are more intelligent:
they never need to consider their responses,
as they're always immediately responding.
In a similar vein, there's no context clues as to what one person said that made others on an episode _take time_ to consider -
which might be important than something that can be responded to immediately.
And of course, as Keenan mentions in his post,
for the producers that _do_ edit their audio,
using Smart Speed removes the potential to appreciate the pauses as they occur,
whether they're within conversation or somewhere else.

Back to Keenan's audioblog, you can clearly hear this listening to the end of the podcast as he slows down for emphasis towards the end.

All of this is much better articulated by Simon Sarris in his [thoughts on audiobooks][simon-sarris-audio-books], where he notes:

> Listening is a skill ... Generally the pacing of nearly all media has quickened. Possibly the delivery of “more, faster” is the result of a too-great respect for novelty as an artistic flourish.

Clearly, none of this is groundbreaking -
but reconsidering using Smart Speed has been a nice change of pace.

🐢

[overcast]: https://overcast.fm
[keenan-hot-take]: https://gkeenan.co/avgb/hot-take-its-okay-if-we-dont-consume-all-of-the-worlds-information-before-we-die
[simon-sarris-audio-books]: https://map.simonsarris.com/p/audiobooks-are-books-and-theyre-also
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[App Defaults]]></title>
    <link href="https://stefan.vanburen.xyz/blog/app-defaults/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/app-defaults/</id>
    <published>2023-11-14T18:17:01-05:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Inspired by <a href="https://defaults.rknight.me">https://defaults.rknight.me</a>, here are my current defaults:</p>
<ul>
<li>Mail Client: Mail.app</li>
<li>Mail Server: <a href="https://migadu.com">Migadu</a></li>
<li>Notes: <a href="https://getdrafts.com">Drafts</a></li>
<li>To-Do: <a href="https://culturedcode.com/things/">Things</a></li>
<li>iPhone Photo Shooting: Camera.app</li>
<li>Photo Management: Photos.app</li>
<li>Calendar: Calendar.app</li>
<li>Cloud File Storage: iCloud Drive</li>
<li><abbr title="Really Simple Syndication">RSS</abbr>: <a href="https://netnewswire.com">NetNewsWire</a> (client) + <a href="https://feedbin.com">Feedbin</a> (server)</li>
<li>Contacts: <a href="https://flexibits.com/cardhop">Cardhop</a></li>
<li>Browser: Safari</li>
<li>Chat: Messages</li>
<li>Bookmarks: <a href="https://pinboard.in">Pinboard</a></li>
<li>Read It Later: <a href="https://www.instapaper.com">Instapaper</a>; transitioning back from Reeder&rsquo;s read-it-later</li>
<li>Word Processing: n/a</li>
<li>Spreadsheets: n/a</li>
<li>Presentations: n/a</li>
<li>Shopping Lists: Grocery</li>
<li>Meal Planning: <a href="https://getdrafts.com">Drafts</a> / <a href="https://mela.recipes">Mela</a></li>
<li>Budgeting and Personal Finance: n/a</li>
<li>News: Hacker News / lobste.rs / text.npr.org</li>
<li>Music: Spotify / <a href="https://brushedtype.co/doppler/">Doppler</a></li>
<li>Podcasts: Overcast</li>
<li>Password Management: 1Password</li>
</ul>
<p>Also see my <a href="/uses">/uses</a> page for other hardware / software!</p>
]]></content>
    <source:markdown><![CDATA[
Inspired by <https://defaults.rknight.me>, here are my current defaults:

* Mail Client: Mail.app
* Mail Server: [Migadu](https://migadu.com)
* Notes: [Drafts](https://getdrafts.com)
* To-Do: [Things](https://culturedcode.com/things/)
* iPhone Photo Shooting: Camera.app
* Photo Management: Photos.app
* Calendar: Calendar.app
* Cloud File Storage: iCloud Drive
* <abbr title="Really Simple Syndication">RSS</abbr>: [NetNewsWire](https://netnewswire.com) (client) + [Feedbin](https://feedbin.com) (server)
* Contacts: [Cardhop](https://flexibits.com/cardhop)
* Browser: Safari
* Chat: Messages
* Bookmarks: [Pinboard](https://pinboard.in)
* Read It Later: [Instapaper](https://www.instapaper.com); transitioning back from Reeder's read-it-later
* Word Processing: n/a
* Spreadsheets: n/a
* Presentations: n/a
* Shopping Lists: Grocery
* Meal Planning: [Drafts](https://getdrafts.com) / [Mela](https://mela.recipes)
* Budgeting and Personal Finance: n/a
* News: Hacker News / lobste.rs / text.npr.org
* Music: Spotify / [Doppler](https://brushedtype.co/doppler/)
* Podcasts: Overcast
* Password Management: 1Password

Also see my [/uses](/uses) page for other hardware / software!
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Style Guide]]></title>
    <link href="https://stefan.vanburen.xyz/blog/style-guide/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/style-guide/</id>
    <published>2023-02-01T19:48:30-05:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Inspired by Louie Mantia&rsquo;s <a href="https://lmnt.me/post/css.html"><abbr title="Cascading Style Sheets">CSS</abbr> for This Blog</a>,
I figured I&rsquo;d publish the page I use locally for testing out how my <abbr title="Cascading Style Sheets">CSS</abbr> looks.</p>
<p>It&rsquo;s over at <a href="/style-guide">/style-guide</a> (<a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz/tree/main/item/content/style-guide.md">source</a>) and it&rsquo;s basically a dump of the markdown tab on <a href="https://www.bryanbraun.com/poor-mans-styleguide/">bryanbraun.com/poor-mans-styleguide</a>. A great, simple way to see the various styles for most everything on the site.</p>
]]></content>
    <source:markdown><![CDATA[
Inspired by Louie Mantia's <a href="https://lmnt.me/post/css.html"><abbr title="Cascading Style Sheets">CSS</abbr> for This Blog</a>,
I figured I'd publish the page I use locally for testing out how my <abbr title="Cascading Style Sheets">CSS</abbr> looks.

It's over at [/style-guide](/style-guide) ([source](https://git.sr.ht/~svbn/stefan.vanburen.xyz/tree/main/item/content/style-guide.md)) and it's basically a dump of the markdown tab on [bryanbraun.com/poor-mans-styleguide](https://www.bryanbraun.com/poor-mans-styleguide/). A great, simple way to see the various styles for most everything on the site.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[November Review]]></title>
    <link href="https://stefan.vanburen.xyz/blog/november-review/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/november-review/</id>
    <published>2022-12-02T12:08:30-05:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>A quick retrospective on my <a href="/blog/november-goals">November Goals</a> post:</p>
<blockquote>
<p>Run 80 miles</p>
</blockquote>
<p>I fell a little short here, running just 58 miles for the month. But it was busier than I gave myself credit for: a trip to see friends and being on call for two straight weeks made things a bit tricky. I <em>should</em> still be in good shape to get to 1000 miles for the year, which would likely be the most I&rsquo;ve clocked in a year, ever.</p>
<blockquote>
<p>No drinking during the week (except Thanksgiving!)</p>
</blockquote>
<p>Mostly good! I might have had ~two days where I went out with friends during the week for a beer (or several), but overall was contained to weekends and Thanksgiving. Going to keep things going into December!</p>
]]></content>
    <source:markdown><![CDATA[
A quick retrospective on my [November Goals](/blog/november-goals) post:

> Run 80 miles

I fell a little short here, running just 58 miles for the month. But it was busier than I gave myself credit for: a trip to see friends and being on call for two straight weeks made things a bit tricky. I _should_ still be in good shape to get to 1000 miles for the year, which would likely be the most I've clocked in a year, ever.

> No drinking during the week (except Thanksgiving!)

Mostly good! I might have had ~two days where I went out with friends during the week for a beer (or several), but overall was contained to weekends and Thanksgiving. Going to keep things going into December!
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Update 2022-11-03]]></title>
    <link href="https://stefan.vanburen.xyz/blog/update-2022-11-03/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/update-2022-11-03/</id>
    <published>2022-11-03T16:13:21-04:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<blockquote>
<p><a href="https://stefan.vanburen.xyz/blog/grease-the-groove/">I’ve been a little hesitant to write here, for whatever reason. Haven’t had the gumption to write, I suppose. So I’d like to start up a bit of a habit — call it a “reverse New Years resolution”: I’d like to write something here (either a blog or TIL) each day until the end of 2022.</a></p>
</blockquote>
<p>s/each day/each week</p>
<p>A bit too much, I think, to post each day. But I&rsquo;m glad to have started up the habit of thinking about what to write again, even if it&rsquo;s inconsequential.</p>
]]></content>
    <source:markdown><![CDATA[
> [I’ve been a little hesitant to write here, for whatever reason. Haven’t had the gumption to write, I suppose. So I’d like to start up a bit of a habit — call it a “reverse New Years resolution”: I’d like to write something here (either a blog or TIL) each day until the end of 2022.](https://stefan.vanburen.xyz/blog/grease-the-groove/)

s/each day/each week

A bit too much, I think, to post each day. But I'm glad to have started up the habit of thinking about what to write again, even if it's inconsequential.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Service Wrappers]]></title>
    <link href="https://stefan.vanburen.xyz/til/service-wrappers/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/service-wrappers/</id>
    <published>2022-11-01T18:54:05-04:00</published>
    <updated>2024-08-16T08:46:52-04:00</updated>
    <content type="html"><![CDATA[<p>TIL of <a href="https://naildrivin5.com/blog/2022/10/31/wrap-third-party-apis-in-service-wrappers-to-simplify-your-code.html">Service Wrappers</a>
a technique I&rsquo;ve used in the past but have never had a name for.
It&rsquo;s essentially an approach to wrapping external APIs.
From the article:</p>
<blockquote>
<ul>
<li>Methods should be named in the language of the service, not the language of the app.</li>
<li>Arguments should use the domain of the service, not the domain of the app.</li>
<li>Arguments should be whatever type is directly needed by the service, so passing in complex stuff like Active Record should be avoided.</li>
<li>The return value should not be a complex object from the third party, but ideally only what data a caller will need (often nothing at all). If it must be a complex object, its name or properties should be in the domain of the service.</li>
</ul>
</blockquote>
<p>The article is written from a Rails perspective,
but is broadly useful &mdash;
always nice to have a name for something useful!</p>
]]></content>
    <source:markdown><![CDATA[
TIL of [Service Wrappers][]
a technique I've used in the past but have never had a name for.
It's essentially an approach to wrapping external APIs.
From the article:

> * Methods should be named in the language of the service, not the language of the app.
> * Arguments should use the domain of the service, not the domain of the app.
> * Arguments should be whatever type is directly needed by the service, so passing in complex stuff like Active Record should be avoided.
> * The return value should not be a complex object from the third party, but ideally only what data a caller will need (often nothing at all). If it must be a complex object, its name or properties should be in the domain of the service.

The article is written from a Rails perspective,
but is broadly useful ---
always nice to have a name for something useful!

[Service Wrappers]: https://naildrivin5.com/blog/2022/10/31/wrap-third-party-apis-in-service-wrappers-to-simplify-your-code.html
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Blair Witch]]></title>
    <link href="https://stefan.vanburen.xyz/blog/blair-witch/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/blair-witch/</id>
    <published>2022-10-31T08:10:13-04:00</published>
    <updated>2024-04-20T16:02:22-04:00</updated>
    <content type="html"><![CDATA[<p>Watched <a href="https://www.imdb.com/title/tt0185937/">&ldquo;The Blair Witch Project&rdquo;</a> last night as a bit of a pre-Halloween treat. Two thoughts:</p>
<ol>
<li>Some movies are just better in theaters.</li>
<li>(Maybe related!): The bar for &ldquo;scary&rdquo; has gotten higher in the last 20 years.</li>
</ol>
<p>Either way, worth the watch. Just don&rsquo;t read the Wikipedia page first, and pay attention &mdash; the found-film effect can be jarring.</p>
]]></content>
    <source:markdown><![CDATA[
Watched ["The Blair Witch Project"](https://www.imdb.com/title/tt0185937/) last night as a bit of a pre-Halloween treat. Two thoughts:

1. Some movies are just better in theaters.
2. (Maybe related!): The bar for "scary" has gotten higher in the last 20 years.

Either way, worth the watch. Just don't read the Wikipedia page first, and pay attention --- the found-film effect can be jarring.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Reading the Web]]></title>
    <link href="https://stefan.vanburen.xyz/blog/reading-the-web/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/reading-the-web/</id>
    <published>2022-10-29T09:48:31-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;ve spent a decent amount of time tweaking my … &ldquo;system&rdquo; for reading articles and blogs on the &rsquo;net that I figured I&rsquo;d jot down what I&rsquo;ve tried, and what&rsquo;s currently working for me.</p>
<p>I currently use two separate but related tools for reading the web: a feed reader, and a &ldquo;read later&rdquo; app. My first feed reader was <a href="https://en.wikipedia.org/wiki/Google_Reader">Google Reader</a> until its untimely demise, followed by a long while of using <a href="https://en.wikipedia.org/wiki/Newsbeuter">newsbeuter</a> to intermittently follow feeds. And for &ldquo;read later&rdquo;, I started with <a href="https://www.instapaper.com">Instapaper</a>, close to the time it was initially released.</p>
<p>Eventually, I stumbled on <a href="https://feedbin.com">Feedbin</a>, which has hosted my feeds ever since. Feedbin is great for many reasons, including: muting feeds; the autogenerated email address for newsletters; the friendliness and <a href="https://craigmod.com/essays/fast_software/">speed</a> of the interface. But I&rsquo;ve never used it as a primary client for actually <em>reading</em> my feeds — I&rsquo;ve always preferred a native client over a web client where I can get one.</p>
<p>And thus, to start with: <a href="https://reeder.app">Reeder.app</a>. Reeder was my go-to feed reader since at least Reeder 3, and it paired nicely with Instapaper.</p>
<p>I stuck with this setup for a long time. Eventually, though, I had ended up with too many saved articles in Instapaper. I tried splitting things out into folders to make &ldquo;Home&rdquo; more manageable; eventually, I settled on a &ldquo;Long&rdquo; folder that was for anything that Instapaper deemed to be over a &ldquo;10 minute read&rdquo;, and everything else was left in &ldquo;Home&rdquo;.</p>
<p>I eventually declared bankruptcy on Instapaper, for a few reasons, not least of which involved Instapaper being primarily web-based (at the end, I could <em>feel</em> the latency of talking to the server to interact with the app at all, despite Instapaper&rsquo;s &ldquo;native&rdquo; apps).</p>
<p>Next, I switched over to Reeder&rsquo;s built-in &ldquo;read later&rdquo; feature, which let me send articles over to a more permanent archive, where they wouldn&rsquo;t be cleared out after having been opened. This was great — using the same app for both &ldquo;triaging&rdquo; the feed and also for reading longer articles. It helped, too, that Reeder is a beautiful and thoughtfully designed app.</p>
<p>But the reading experience wasn&rsquo;t without bugs: for longer articles, if I switched away from the app while reading, it would very regularly lose my place in the article. And with the app being developed by a single person (as far as I know) with seemingly most of their focus on their new (similarly fantastic) recipe app <a href="https://mela.recipes">Mela</a>, I wasn&rsquo;t sure things would get fixed in the near future.</p>
<p>After trying out a few alternative clients, I settled on <a href="https://netnewswire.com">NetNewsWire</a> (<abbr title="NetNewsWire">NNW</abbr>). <abbr title="NetNewsWire">NNW</abbr> <em>feels</em> native. And simple. It&rsquo;s free and open source (still (reasonably) <a href="https://github.com/Ranchero-Software/NetNewsWire/commits/main">actively developed</a>), and has apps for both macOS and iOS. Truly <a href="https://daringfireball.net/linked/2020/03/20/mac-assed-mac-apps">Mac-assed</a>.</p>
<p><abbr title="NetNewsWire">NNW</abbr> doesn&rsquo;t have a &ldquo;read later&rdquo; feature, but/and I&rsquo;ve come to like that: I now only use Reeder as a &ldquo;read later&rdquo; app, and send articles to it from <abbr title="NetNewsWire">NNW</abbr>. And funnily enough, the extra bit of friction of <em>not</em> having a keyboard shortcut to send to Reeder&rsquo;s &ldquo;read later&rdquo; (instead, needing to use <abbr title="NetNewsWire">NNW</abbr>&rsquo;s share menu) has kept me from adding too many articles, and makes me more considerate of what I add to my &ldquo;read later&rdquo; queue.</p>
<p>Anyway — I wouldn&rsquo;t call this setup perfect, but it&rsquo;s been working out well for me over the past year or so since I&rsquo;ve adopted it. Feedbin (feed sync and management) → <abbr title="NetNewsWire">NNW</abbr> (client) → Reeder (read later).</p>
<p>One important caveat in this whole setup: I only use the macOS version of <abbr title="NetNewsWire">NNW</abbr>, even though there&rsquo;s an iOS version, because I like to keep the number of &ldquo;inboxes&rdquo; and consumption opportunities on my phone to a minimum. I&rsquo;ll talk about that more in a later post!</p>
]]></content>
    <source:markdown><![CDATA[
I've spent a decent amount of time tweaking my … "system" for reading articles and blogs on the 'net that I figured I'd jot down what I've tried, and what's currently working for me.

I currently use two separate but related tools for reading the web: a feed reader, and a "read later" app. My first feed reader was [Google Reader](https://en.wikipedia.org/wiki/Google_Reader) until its untimely demise, followed by a long while of using [newsbeuter](https://en.wikipedia.org/wiki/Newsbeuter) to intermittently follow feeds. And for "read later", I started with [Instapaper](https://www.instapaper.com), close to the time it was initially released.

Eventually, I stumbled on [Feedbin](https://feedbin.com), which has hosted my feeds ever since. Feedbin is great for many reasons, including: muting feeds; the autogenerated email address for newsletters; the friendliness and [speed](https://craigmod.com/essays/fast_software/) of the interface. But I've never used it as a primary client for actually _reading_ my feeds — I've always preferred a native client over a web client where I can get one.

And thus, to start with: [Reeder.app](https://reeder.app). Reeder was my go-to feed reader since at least Reeder 3, and it paired nicely with Instapaper.

I stuck with this setup for a long time. Eventually, though, I had ended up with too many saved articles in Instapaper. I tried splitting things out into folders to make "Home" more manageable; eventually, I settled on a "Long" folder that was for anything that Instapaper deemed to be over a "10 minute read", and everything else was left in "Home".

I eventually declared bankruptcy on Instapaper, for a few reasons, not least of which involved Instapaper being primarily web-based (at the end, I could _feel_ the latency of talking to the server to interact with the app at all, despite Instapaper's "native" apps).

Next, I switched over to Reeder's built-in "read later" feature, which let me send articles over to a more permanent archive, where they wouldn't be cleared out after having been opened. This was great — using the same app for both "triaging" the feed and also for reading longer articles. It helped, too, that Reeder is a beautiful and thoughtfully designed app.

But the reading experience wasn't without bugs: for longer articles, if I switched away from the app while reading, it would very regularly lose my place in the article. And with the app being developed by a single person (as far as I know) with seemingly most of their focus on their new (similarly fantastic) recipe app [Mela](https://mela.recipes), I wasn't sure things would get fixed in the near future.

After trying out a few alternative clients, I settled on [NetNewsWire](https://netnewswire.com) (<abbr title="NetNewsWire">NNW</abbr>). <abbr title="NetNewsWire">NNW</abbr> _feels_ native. And simple. It's free and open source (still (reasonably) [actively developed](https://github.com/Ranchero-Software/NetNewsWire/commits/main)), and has apps for both macOS and iOS. Truly [Mac-assed](https://daringfireball.net/linked/2020/03/20/mac-assed-mac-apps).

<abbr title="NetNewsWire">NNW</abbr> doesn't have a "read later" feature, but/and I've come to like that: I now only use Reeder as a "read later" app, and send articles to it from <abbr title="NetNewsWire">NNW</abbr>. And funnily enough, the extra bit of friction of _not_ having a keyboard shortcut to send to Reeder's "read later" (instead, needing to use <abbr title="NetNewsWire">NNW</abbr>'s share menu) has kept me from adding too many articles, and makes me more considerate of what I add to my "read later" queue.

Anyway — I wouldn't call this setup perfect, but it's been working out well for me over the past year or so since I've adopted it. Feedbin (feed sync and management) → <abbr title="NetNewsWire">NNW</abbr> (client) → Reeder (read later).

One important caveat in this whole setup: I only use the macOS version of <abbr title="NetNewsWire">NNW</abbr>, even though there's an iOS version, because I like to keep the number of "inboxes" and consumption opportunities on my phone to a minimum. I'll talk about that more in a later post!
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[November Goals]]></title>
    <link href="https://stefan.vanburen.xyz/blog/november-goals/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/november-goals/</id>
    <published>2022-10-28T20:00:30-04:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>Setting some November goals a little early:</p>
<ol>
<li>
<p>Run 80 miles</p>
<p>I set a goal of 500 miles this year — I&rsquo;m already at 864 miles for the year, most of which I ran in the early summer. I&rsquo;d really like to pass 1000 for the first time in a long while, and if I&rsquo;m not at 950 by December, I can tell I&rsquo;m not going to make it. 20 miles a week should be reasonable, given that I was getting up to 30-40 during the summer.</p>
</li>
<li>
<p>No drinking during the week (except Thanksgiving!)</p>
<p>I&rsquo;d like to drink a bit less — with the ramp up on running, and an on-call shift or two coming up, I&rsquo;ll be avoiding drinking during the week, up until the holiday later on next month.</p>
</li>
</ol>
]]></content>
    <source:markdown><![CDATA[
Setting some November goals a little early:

1. Run 80 miles

   I set a goal of 500 miles this year — I'm already at 864 miles for the year, most of which I ran in the early summer. I'd really like to pass 1000 for the first time in a long while, and if I'm not at 950 by December, I can tell I'm not going to make it. 20 miles a week should be reasonable, given that I was getting up to 30-40 during the summer.

1. No drinking during the week (except Thanksgiving!)

   I'd like to drink a bit less — with the ramp up on running, and an on-call shift or two coming up, I'll be avoiding drinking during the week, up until the holiday later on next month.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Grease the Groove]]></title>
    <link href="https://stefan.vanburen.xyz/blog/grease-the-groove/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/grease-the-groove/</id>
    <published>2022-10-27T19:51:21-04:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;ve been a little hesitant to write here, for whatever reason. Haven&rsquo;t had the gumption to write, I suppose. So I&rsquo;d like to start up a bit of a habit — call it a &ldquo;reverse New Years resolution&rdquo;: I&rsquo;d like to write something here (either a blog or TIL) each day until the end of 2022.</p>
<p>Grease the Groove, they say. Don&rsquo;t worry about who&rsquo;s reading (certainly not many!), and write like I&rsquo;m writing to <em>one person</em>.</p>
<p>We&rsquo;ll see how it goes.</p>
]]></content>
    <source:markdown><![CDATA[
I've been a little hesitant to write here, for whatever reason. Haven't had the gumption to write, I suppose. So I'd like to start up a bit of a habit — call it a "reverse New Years resolution": I'd like to write something here (either a blog or TIL) each day until the end of 2022.

Grease the Groove, they say. Don't worry about who's reading (certainly not many!), and write like I'm writing to _one person_.

We'll see how it goes.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Go&rsquo;s fmt package Explicit Argument Indexes]]></title>
    <link href="https://stefan.vanburen.xyz/til/fmt-explicit-argument-indexes/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/fmt-explicit-argument-indexes/</id>
    <published>2022-06-24T15:59:17-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Not sure how I hadn&rsquo;t seen this before:
in Go, you can specify the index of the format argument you want to use for each formatting verb.</p>
<p>Simply enough, from the <a href="https://pkg.go.dev/fmt#hdr-Explicit_argument_indexes">package docs</a>:</p>
<pre><code class="language-go">fmt.Sprintf(&quot;%[2]d %[1]d\n&quot;, 11, 22) // =&gt; &quot;22 11&quot;
</code></pre>
<p>In the past, I&rsquo;ve actually done something along the lines of:</p>
<pre><code class="language-go">s := &quot;test&quot;
fmt.Sprintf(&quot;%s %s&quot;, s, s) // =&gt; &quot;test test&quot;
</code></pre>
<p>Instead, you can do:</p>
<pre><code class="language-go">s := &quot;test&quot;
fmt.Sprintf(&quot;%[1]s %[1]s&quot;, s) // =&gt; &quot;test test&quot;
</code></pre>
<p>This came up today at $WORK while providing a table name to a constructed <abbr title="Structured Query Language">SQL</abbr> query -
the table name needed to be supplied multiple times in an upsert, so we could simply do:</p>
<pre><code class="language-go">query := fmt.Sprintf(`
INSERT INTO %[1]s (...) VALUES ($1, $2)
ON CONFLICT (...) DO UPDATE SET ... = %[1]s.(...) WHERE %[1]s.(...) = $2;
`, tableName)
</code></pre>
<p>Instead of needing to supply <code>tableName</code> three times,
it could be supplied once using <code>%[1]s</code>.</p>
]]></content>
    <source:markdown><![CDATA[
Not sure how I hadn't seen this before:
in Go, you can specify the index of the format argument you want to use for each formatting verb.

Simply enough, from the [package docs](https://pkg.go.dev/fmt#hdr-Explicit_argument_indexes):

```go
fmt.Sprintf("%[2]d %[1]d\n", 11, 22) // => "22 11"
```

In the past, I've actually done something along the lines of:

```go
s := "test"
fmt.Sprintf("%s %s", s, s) // => "test test"
```

Instead, you can do:

```go
s := "test"
fmt.Sprintf("%[1]s %[1]s", s) // => "test test"
```

This came up today at $WORK while providing a table name to a constructed <abbr title="Structured Query Language">SQL</abbr> query -
the table name needed to be supplied multiple times in an upsert, so we could simply do:

```go
query := fmt.Sprintf(`
INSERT INTO %[1]s (...) VALUES ($1, $2)
ON CONFLICT (...) DO UPDATE SET ... = %[1]s.(...) WHERE %[1]s.(...) = $2;
`, tableName)
```

Instead of needing to supply `tableName` three times,
it could be supplied once using `%[1]s`.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[SQLite Connection Configuration]]></title>
    <link href="https://stefan.vanburen.xyz/til/sqlite-connection-configuration/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/sqlite-connection-configuration/</id>
    <published>2022-04-02T10:54:44-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>These notes are basically cribbed from Ben Johnson&rsquo;s excellent &ldquo;Building Production Applications Using Go &amp; SQLite&rdquo; GopherCon talk,
specifically the <a href="https://youtu.be/XcAYkriuQ1o?t=430">&ldquo;Configuring SQLite&rdquo; section</a>.</p>
<p>Generally speaking, you&rsquo;ll want to set three <code>PRAGMA</code>s on each SQLite connection:</p>
<pre><code class="language-sql">PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
</code></pre>
<p>In turn,</p>
<ul>
<li>
<p><a href="https://sqlite.org/pragma.html#pragma_journal_mode"><code>PRAGMA journal_mode = WAL;</code></a> sets the database into <a href="https://www.sqlite.org/wal.html"><abbr title="Write-Ahead Logging">WAL</abbr> journaling mode</a>.
Unlike the others,
this doesn&rsquo;t necessarily need to be set on each connection;
once a database is in <a href="https://www.sqlite.org/wal.html"><abbr title="Write-Ahead Logging">WAL</abbr></a> mode <a href="https://sqlite.org/wal.html#persistence_of_wal_mode">it&rsquo;ll stay in that mode across database connections</a>.
<a href="https://www.sqlite.org/wal.html"><abbr title="Write-Ahead Logging">WAL</abbr></a> journaling mode is recommended for most SQLite server applications,
because it makes writers not block readers.</p>
</li>
<li>
<p><a href="https://sqlite.org/pragma.html#pragma_busy_timeout"><code>PRAGMA busy_timeout = 5000;</code></a> sets the <a href="https://sqlite.org/c3ref/busy_timeout.html">busy_timeout</a> to 5000 milliseconds (5 seconds).
Without this setting,
if a write transaction is running,
and another write transaction starts,
the second write transaction will fail immediately.</p>
</li>
<li>
<p><a href="https://sqlite.org/pragma.html#pragma_foreign_keys"><code>PRAGMA foreign_keys = ON;</code></a> turns on SQLite&rsquo;s <a href="https://sqlite.org/foreignkeys.html">foreign key support</a>.
Foreign Keys aren&rsquo;t enabled by default in SQLite for historical reasons,
but foreign keys are great for maintaining data integrity.
So &ndash; use them!</p>
</li>
</ul>
<hr>
<ins datetime="2024-06-30">
  Update(2024-06-30): A more complete discussion of these settings and more can be found over at
  <a href="https://kerkour.com/sqlite-for-servers">kerkour.com/sqlite-for-servers</a>
</ins>
]]></content>
    <source:markdown><![CDATA[
These notes are basically cribbed from Ben Johnson's excellent "Building Production Applications Using Go & SQLite" GopherCon talk,
specifically the ["Configuring SQLite" section](https://youtu.be/XcAYkriuQ1o?t=430).

Generally speaking, you'll want to set three `PRAGMA`s on each SQLite connection:

```sql
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
```

In turn,

* [`PRAGMA journal_mode = WAL;`][pragma-journal-mode] sets the database into <a href="https://www.sqlite.org/wal.html"><abbr title="Write-Ahead Logging">WAL</abbr> journaling mode</a>.
  Unlike the others,
  this doesn't necessarily need to be set on each connection;
  once a database is in <a href="https://www.sqlite.org/wal.html"><abbr title="Write-Ahead Logging">WAL</abbr></a> mode [it'll stay in that mode across database connections](https://sqlite.org/wal.html#persistence_of_wal_mode).
  <a href="https://www.sqlite.org/wal.html"><abbr title="Write-Ahead Logging">WAL</abbr></a> journaling mode is recommended for most SQLite server applications,
  because it makes writers not block readers.

* [`PRAGMA busy_timeout = 5000;`][pragma-busy-timeout] sets the [busy_timeout](https://sqlite.org/c3ref/busy_timeout.html) to 5000 milliseconds (5 seconds).
  Without this setting,
  if a write transaction is running,
  and another write transaction starts,
  the second write transaction will fail immediately.

* [`PRAGMA foreign_keys = ON;`][pragma-foreign-keys] turns on SQLite's [foreign key support](https://sqlite.org/foreignkeys.html).
  Foreign Keys aren't enabled by default in SQLite for historical reasons,
  but foreign keys are great for maintaining data integrity.
  So -- use them!

[pragma-journal-mode]: https://sqlite.org/pragma.html#pragma_journal_mode
[pragma-foreign-keys]: https://sqlite.org/pragma.html#pragma_foreign_keys
[pragma-busy-timeout]: https://sqlite.org/pragma.html#pragma_busy_timeout

* * *

<ins datetime="2024-06-30">
  Update(2024-06-30): A more complete discussion of these settings and more can be found over at
  <a href="https://kerkour.com/sqlite-for-servers">kerkour.com/sqlite-for-servers</a>
</ins>
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Shell in Markdown]]></title>
    <link href="https://stefan.vanburen.xyz/til/markdown-shell/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/markdown-shell/</id>
    <published>2022-01-28T16:23:20-05:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>For a long time,
I&rsquo;ve used <code>commandline</code> as the name for the syntax of code fences in markdown whenever I&rsquo;ve wanted to denote a shell command or session.</p>
<p>I&rsquo;ve now realized that I&rsquo;ve been using the wrong name,
and also that there are two separate scenarios to keep in mind:</p>
<hr>
<p>The first scenario is one-off commands, which should use the syntax <a href="https://github.com/github/linguist/blob/73e2d735c3c26577fc89c1cb3f8342e5a8ff1d82/lib/linguist/languages.yml#L5602"><code>sh</code>, or <code>shell</code></a>.
This would be showing something you&rsquo;d enter directly at the command line.</p>
<p>For example:</p>
<pre><code class="language-sh">brew install git
</code></pre>
<p>I leave out the <a href="https://en.wikipedia.org/wiki/Command-line_interface#Command_prompt">command prompt</a>,
because it&rsquo;s implied by the context that the line is to be entered in a command line.
Also, it helps with copy-paste.</p>
<hr>
<p>The other scenario is showing a shell <em>session</em>,
which typically shows the command prompt, an entered command, and <em>the response to the command</em>.
In this instance I&rsquo;d use the <a href="https://github.com/github/linguist/blob/cddf7476af4c95d1572956ffc5c0cb84f7e431c5/lib/linguist/languages.yml#L5921"><code>console</code>, or <code>sh-session</code></a> syntax.</p>
<p>For example:</p>
<pre><code class="language-console">$ git st
?? content/til/markdown-shell.md
</code></pre>
<p>🐚</p>
]]></content>
    <source:markdown><![CDATA[
For a long time,
I've used `commandline` as the name for the syntax of code fences in markdown whenever I've wanted to denote a shell command or session.

I've now realized that I've been using the wrong name,
and also that there are two separate scenarios to keep in mind:

* * *

The first scenario is one-off commands, which should use the syntax [`sh`, or `shell`](https://github.com/github/linguist/blob/73e2d735c3c26577fc89c1cb3f8342e5a8ff1d82/lib/linguist/languages.yml#L5602).
This would be showing something you'd enter directly at the command line.

For example:

```sh
brew install git
```

I leave out the [command prompt](https://en.wikipedia.org/wiki/Command-line_interface#Command_prompt),
because it's implied by the context that the line is to be entered in a command line.
Also, it helps with copy-paste.

* * *

The other scenario is showing a shell _session_,
which typically shows the command prompt, an entered command, and _the response to the command_.
In this instance I'd use the [`console`, or `sh-session`](https://github.com/github/linguist/blob/cddf7476af4c95d1572956ffc5c0cb84f7e431c5/lib/linguist/languages.yml#L5921) syntax.

For example:

```console
$ git st
?? content/til/markdown-shell.md
```

🐚
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[PostgreSQL and jsonb type casts]]></title>
    <link href="https://stefan.vanburen.xyz/til/postgres-jsonb-casting/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/postgres-jsonb-casting/</id>
    <published>2022-01-19T18:30:21-05:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Found a bug in one of our systems at work that deals with <a href="https://www.postgresql.org/docs/current/datatype-json.html">jsonb data</a> stored in Postgres today.
The quick summary is that one of our systems was looking for the maximum numerical value of a jsonb integer in a table.</p>
<p>A very simplified example:</p>
<pre><code class="language-postgresql">SELECT
  max(balances)
FROM
  unnest(ARRAY[
    '{&quot;balance&quot;: 7}'::jsonb-&gt;&gt;'balance',
    '{&quot;balance&quot;: 17}'::jsonb-&gt;&gt;'balance'
  ]) as balances
</code></pre>
<p>This returns <code>7</code>, because while the &ldquo;balance&rdquo; field is a numeric <abbr title="JavaScript Object Notation">JSON</abbr> value, the <a href="https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING"><code>-&gt;&gt;</code> operator</a> turns the value into text! &mdash; which makes the comparison a lexicographical one.</p>
<p>Instead, you need to make sure to <a href="https://www.postgresql.org/docs/current/sql-expressions.html#SQL-SYNTAX-TYPE-CASTS">cast the values</a> before comparing:</p>
<pre><code class="language-postgresql">SELECT
  max(balances)
FROM
  unnest(ARRAY[
    ('{&quot;balance&quot;: 7}'::jsonb-&gt;&gt;'balance')::integer,
    ('{&quot;balance&quot;: 17}'::jsonb-&gt;&gt;'balance')::integer
  ]) as balances
</code></pre>
<p>Which returns the expected <code>17</code>.</p>
]]></content>
    <source:markdown><![CDATA[
Found a bug in one of our systems at work that deals with [jsonb data](https://www.postgresql.org/docs/current/datatype-json.html) stored in Postgres today.
The quick summary is that one of our systems was looking for the maximum numerical value of a jsonb integer in a table.

A very simplified example:

```postgresql
SELECT
  max(balances)
FROM
  unnest(ARRAY[
    '{"balance": 7}'::jsonb->>'balance',
    '{"balance": 17}'::jsonb->>'balance'
  ]) as balances
```

This returns `7`, because while the "balance" field is a numeric <abbr title="JavaScript Object Notation">JSON</abbr> value, the [`->>` operator](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING) turns the value into text! --- which makes the comparison a lexicographical one.

Instead, you need to make sure to [cast the values](https://www.postgresql.org/docs/current/sql-expressions.html#SQL-SYNTAX-TYPE-CASTS) before comparing:

```postgresql
SELECT
  max(balances)
FROM
  unnest(ARRAY[
    ('{"balance": 7}'::jsonb->>'balance')::integer,
    ('{"balance": 17}'::jsonb->>'balance')::integer
  ]) as balances
```

Which returns the expected `17`.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[PostgreSQL IN versus ANY]]></title>
    <link href="https://stefan.vanburen.xyz/til/psql-in-versus-any/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/psql-in-versus-any/</id>
    <published>2022-01-11T08:40:46-05:00</published>
    <updated>2025-11-16T19:49:53-05:00</updated>
    <content type="html"><![CDATA[<p>I still feel rather new to PostgreSQL,
and a few times now in Go I&rsquo;ve tried to do something along the lines of:</p>
<pre><code class="language-go">type pgdb struct {
	db *sql.DB
}

func (db *pgdb) getEntityByID(ctx context.Context, ids []string) ([]Entity, error) {
	query := &quot;SELECT * FROM entities WHERE entities.id IN ($1)&quot;

	rows, err := db.db.QueryContext(ctx, query, pq.StringArray(ids))
	/// ...
}
</code></pre>
<p>But the query would always fail or retrieve no rows.
I&rsquo;ve always fixed it by switching the <code>IN $1</code> for a <code>= ANY($1)</code>,
but never quite understood why.</p>
<p>Researching this a bit more today,
I realized that the <a href="https://www.postgresql.org/docs/current/functions-comparisons.html#FUNCTIONS-COMPARISONS-IN-SCALAR"><code>IN</code></a> function takes a list of literals,
whereas <a href="https://www.postgresql.org/docs/current/functions-comparisons.html#id-1.5.8.30.16"><code>ANY</code></a> is an array function that takes a single array literal.
Thus, when I&rsquo;m passing a <code>pq.StringArray</code>, it only works with the array function.</p>
<hr>
<p>I found this out going through <a href="https://github.com/lib/pq">lib/pq</a>&rsquo;s issues,
finding <a href="https://github.com/lib/pq/issues/600">this issue</a> with <a href="https://github.com/lib/pq/issues/600#issuecomment-294143990">this comment</a> explaining the difference quite well,
which also pointed to a <a href="https://github.com/lib/pq/issues/515">simpler case</a> where <a href="https://github.com/lib/pq/issues/515#issuecomment-252393112">this comment</a> cleared things up for me.</p>
]]></content>
    <source:markdown><![CDATA[
I still feel rather new to PostgreSQL,
and a few times now in Go I've tried to do something along the lines of:

```go
type pgdb struct {
	db *sql.DB
}

func (db *pgdb) getEntityByID(ctx context.Context, ids []string) ([]Entity, error) {
	query := "SELECT * FROM entities WHERE entities.id IN ($1)"

	rows, err := db.db.QueryContext(ctx, query, pq.StringArray(ids))
	/// ...
}
```

But the query would always fail or retrieve no rows.
I've always fixed it by switching the `IN $1` for a `= ANY($1)`,
but never quite understood why.

Researching this a bit more today,
I realized that the [`IN`] function takes a list of literals,
whereas [`ANY`] is an array function that takes a single array literal.
Thus, when I'm passing a `pq.StringArray`, it only works with the array function.

* * *

I found this out going through [lib/pq](https://github.com/lib/pq)'s issues,
finding [this issue](https://github.com/lib/pq/issues/600) with [this comment](https://github.com/lib/pq/issues/600#issuecomment-294143990) explaining the difference quite well,
which also pointed to a [simpler case](https://github.com/lib/pq/issues/515) where [this comment](https://github.com/lib/pq/issues/515#issuecomment-252393112) cleared things up for me.

[`IN`]: https://www.postgresql.org/docs/current/functions-comparisons.html#FUNCTIONS-COMPARISONS-IN-SCALAR
[`ANY`]: https://www.postgresql.org/docs/current/functions-comparisons.html#id-1.5.8.30.16
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Go&rsquo;s package fmt %q verb for strings]]></title>
    <link href="https://stefan.vanburen.xyz/til/gofmt-q/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/gofmt-q/</id>
    <published>2021-09-23T10:00:45-04:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>I wasn&rsquo;t aware of the Go <code>fmt</code> package <code>%q</code> verb, for quoting strings.
Typically, I&rsquo;ve been escaping the quotes manually and using the <code>%s</code> verb:</p>
<pre><code class="language-go">fmt.Sprintf(&quot;this needs quoting: \&quot;%s\&quot;&quot;, &quot;input&quot;)
</code></pre>
<p>But the <code>%q</code> verb obviates the need for that manual quoting.</p>
<p>From the <a href="https://pkg.go.dev/fmt">package documentation</a>:</p>
<blockquote>
<p>%s    the uninterpreted bytes of the string or slice</p>
<p>%q    a double-quoted string safely escaped with Go syntax</p>
</blockquote>
<p>And a quick example (on the <a href="https://play.golang.org/p/w-DfnVMSi_k">Go Playground</a>):</p>
<pre><code class="language-go">package main

import (
	&quot;fmt&quot;
)

func main() {
	test := &quot;I'm a test string&quot;

	fmt.Printf(&quot;\&quot;%s\&quot;&quot;, test)
	// Output: &quot;I'm a test string&quot;
	fmt.Println()
	fmt.Printf(&quot;%q&quot;, test)
	// Output: &quot;I'm a test string&quot;
}
</code></pre>
]]></content>
    <source:markdown><![CDATA[
I wasn't aware of the Go `fmt` package `%q` verb, for quoting strings.
Typically, I've been escaping the quotes manually and using the `%s` verb:

```go
fmt.Sprintf("this needs quoting: \"%s\"", "input")
```

But the `%q` verb obviates the need for that manual quoting.

From the [package documentation](https://pkg.go.dev/fmt):

> %s    the uninterpreted bytes of the string or slice
>
> %q    a double-quoted string safely escaped with Go syntax

And a quick example (on the [Go Playground](https://play.golang.org/p/w-DfnVMSi_k)):

```go
package main

import (
	"fmt"
)

func main() {
	test := "I'm a test string"

	fmt.Printf("\"%s\"", test)
	// Output: "I'm a test string"
	fmt.Println()
	fmt.Printf("%q", test)
	// Output: "I'm a test string"
}
```
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Seven GUIs]]></title>
    <link href="https://stefan.vanburen.xyz/blog/seven-guis/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/seven-guis/</id>
    <published>2021-09-10T12:29:17-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Recently I polished up and published my seven-guis project.
It&rsquo;s an implementation of the <a href="https://eugenkiss.github.io/7guis/">7<abbr title="Graphical User Interfaces">GUIs</abbr> &ldquo;<abbr title="Graphical User Interface">GUI</abbr> Programming Benchmark&rdquo;</a>, written in <a href="https://clojurescript.org/">ClojureScript</a> and <a href="https://github.com/reagent-project/reagent">Reagent</a>.</p>
<p>It&rsquo;s not quite complete - there are <a href="https://eugenkiss.github.io/7guis/tasks">7 tasks</a>, and I finished all except for the last one (the spreadsheet - the most vaguely specified and open-ended, and the most difficult).
I&rsquo;ll finish it down the road, but for now I&rsquo;ve hosted it at <a href="https://seven-guis.vanburen.xyz">seven-guis.vanburen.xyz</a>, and the source code is public, at <a href="https://github.com/stefanvanburen/seven-guis">github.com/stefanvanburen/seven-guis</a>.</p>
]]></content>
    <source:markdown><![CDATA[
Recently I polished up and published my seven-guis project.
It's an implementation of the <a href="https://eugenkiss.github.io/7guis/">7<abbr title="Graphical User Interfaces">GUIs</abbr> "<abbr title="Graphical User Interface">GUI</abbr> Programming Benchmark"</a>, written in [ClojureScript](https://clojurescript.org/) and [Reagent](https://github.com/reagent-project/reagent).

It's not quite complete - there are [7 tasks](https://eugenkiss.github.io/7guis/tasks), and I finished all except for the last one (the spreadsheet - the most vaguely specified and open-ended, and the most difficult).
I'll finish it down the road, but for now I've hosted it at [seven-guis.vanburen.xyz](https://seven-guis.vanburen.xyz), and the source code is public, at [github.com/stefanvanburen/seven-guis](https://github.com/stefanvanburen/seven-guis).
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Go Templates Whitespace]]></title>
    <link href="https://stefan.vanburen.xyz/til/go-templates-whitespace/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/go-templates-whitespace/</id>
    <published>2021-08-31T18:41:20-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>When writing <abbr title="HyperText Markup Language">HTML</abbr> templates in Go, knowing about the <a href="https://pkg.go.dev/text/template#hdr-Text_and_spaces">whitespace modifiers</a> is crucial for keeping both the templates and the output <abbr title="HyperText Markup Language">HTML</abbr> looking good.</p>
<p>For example, if you had the following template, with <code>.SomeText</code> being <code>&quot;foo&quot;</code> and <code>.Something</code> being <code>true</code>:</p>
<pre><code class="language-go-html-template">&lt;a href=&quot;/posts&quot;&gt;
  &lt;span&gt;
  {{ if .Something }}
  Text
  {{ else }}
  Image
  {{ end }}
  &lt;/span&gt;
  {{ .SomeText }}
&lt;/a&gt;
</code></pre>
<p>The actual output <abbr title="HyperText Markup Language">HTML</abbr> would look something like:</p>
<pre><code class="language-html">&lt;a href=&quot;/posts&quot;&gt;&lt;span&gt; Text &lt;/span&gt; foo &lt;/a&gt;
</code></pre>
<p>Which isn&rsquo;t what you want, because there&rsquo;s extra spaces &mdash; both around &ldquo;Text&rdquo; and around &ldquo;foo&rdquo;.</p>
<p>Instead, you could do:</p>
<pre><code class="language-go-html-template">&lt;a href=&quot;/posts&quot;&gt;
  &lt;span&gt;
  {{ if .Something }}
  {{- Text -}}
  {{ else }}
  {{- Image -}}
  {{ end }}
  &lt;/span&gt;
  {{- .SomeText -}}
&lt;/a&gt;
</code></pre>
<p>to get</p>
<pre><code class="language-html">&lt;a href=&quot;/posts&quot;&gt;&lt;span&gt;Text&lt;/span&gt;foo&lt;/a&gt;
</code></pre>
<p>Which makes both the source code and <abbr title="HyperText Markup Language">HTML</abbr> nice and neat.</p>
]]></content>
    <source:markdown><![CDATA[
When writing <abbr title="HyperText Markup Language">HTML</abbr> templates in Go, knowing about the [whitespace modifiers](https://pkg.go.dev/text/template#hdr-Text_and_spaces) is crucial for keeping both the templates and the output <abbr title="HyperText Markup Language">HTML</abbr> looking good.

For example, if you had the following template, with `.SomeText` being `"foo"` and `.Something` being `true`:

```go-html-template
<a href="/posts">
  <span>
  {{ if .Something }}
  Text
  {{ else }}
  Image
  {{ end }}
  </span>
  {{ .SomeText }}
</a>
```

The actual output <abbr title="HyperText Markup Language">HTML</abbr> would look something like:

```html
<a href="/posts"><span> Text </span> foo </a>
```

Which isn't what you want, because there's extra spaces --- both around "Text" and around "foo".

Instead, you could do:

```go-html-template
<a href="/posts">
  <span>
  {{ if .Something }}
  {{- Text -}}
  {{ else }}
  {{- Image -}}
  {{ end }}
  </span>
  {{- .SomeText -}}
</a>
```

to get

```html
<a href="/posts"><span>Text</span>foo</a>
```

Which makes both the source code and <abbr title="HyperText Markup Language">HTML</abbr> nice and neat.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Uses]]></title>
    <link href="https://stefan.vanburen.xyz/blog/uses/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/uses/</id>
    <published>2021-08-16T21:28:11-04:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;ve added a new <a href="/uses">uses</a> page, detailing my personal computing setup.
I&rsquo;m hoping to make it more prose-like in the future, but for now it lists my current most commonly used hardware and software.</p>
]]></content>
    <source:markdown><![CDATA[
I've added a new [uses](/uses) page, detailing my personal computing setup.
I'm hoping to make it more prose-like in the future, but for now it lists my current most commonly used hardware and software.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[<code>time.Ticker</code> in Go]]></title>
    <link href="https://stefan.vanburen.xyz/til/ticker/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/ticker/</id>
    <published>2021-05-21T20:25:49-04:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>The <a href="https://pkg.go.dev/time#Ticker"><code>time.Ticker</code></a> type in Go is incredibly useful for situations in which <a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">polling</a> is needed.</p>
<p>The easiest way to use the Ticker is via the <a href="https://pkg.go.dev/time#example-Tick"><code>time.Tick</code></a> function,
which just provides access to the ticking channel,
which makes it easy to <code>range</code> over:</p>
<pre><code class="language-go">package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	for currentTime := range time.Tick(time.Second) {
		fmt.Println(&quot;current time: &quot;, currentTime)
	}
}
</code></pre>
<p>For more control, <a href="https://pkg.go.dev/time#NewTicker"><code>time.NewTicker</code></a> works wonders &mdash;
there&rsquo;s no better example than the one <a href="https://go.dev/play/p/nG23A6LEd19">from the docs</a>!</p>
]]></content>
    <source:markdown><![CDATA[
The [`time.Ticker`](https://pkg.go.dev/time#Ticker) type in Go is incredibly useful for situations in which [polling](https://en.wikipedia.org/wiki/Polling_(computer_science)) is needed.

The easiest way to use the Ticker is via the [`time.Tick`](https://pkg.go.dev/time#example-Tick) function,
which just provides access to the ticking channel,
which makes it easy to `range` over:

```go
package main

import (
	"fmt"
	"time"
)

func main() {
	for currentTime := range time.Tick(time.Second) {
		fmt.Println("current time: ", currentTime)
	}
}
```

For more control, [`time.NewTicker`](https://pkg.go.dev/time#NewTicker) works wonders ---
there's no better example than the one [from the docs](https://go.dev/play/p/nG23A6LEd19)!
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[<code>run</code> vs <code>errgroup</code>]]></title>
    <link href="https://stefan.vanburen.xyz/til/errgroup-vs-run/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/errgroup-vs-run/</id>
    <published>2021-05-11T10:38:26-04:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>I typically don&rsquo;t do much with concurrency in Go, but messed around this morning with both <a href="https://github.com/oklog/run">oklog/run</a> and <a href="https://golang.org/x/sync/errgroup">sync/errgroup</a>.</p>
<p>I&rsquo;ve defaulted to using oklog/run in the past, but I realized that while they have similar interfaces, they&rsquo;re useful for different things:</p>
<ul>
<li>oklog/run is useful for long-running processes.
<ul>
<li>think starting up http servers alongside other components, and waiting for one to finish, in which case all other components are signalled to finish.</li>
</ul>
</li>
<li>sync/errgroup is useful for short-lived, parallel processes.
<ul>
<li>think units of work to be done in parallel, where one process resulting in an error should stop the entire computation.</li>
</ul>
</li>
</ul>
]]></content>
    <source:markdown><![CDATA[
I typically don't do much with concurrency in Go, but messed around this morning with both [oklog/run](https://github.com/oklog/run) and [sync/errgroup](https://golang.org/x/sync/errgroup).

I've defaulted to using oklog/run in the past, but I realized that while they have similar interfaces, they're useful for different things:

* oklog/run is useful for long-running processes.
  * think starting up http servers alongside other components, and waiting for one to finish, in which case all other components are signalled to finish.
* sync/errgroup is useful for short-lived, parallel processes.
  * think units of work to be done in parallel, where one process resulting in an error should stop the entire computation.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Use <code>jq</code> to see if a JSON key exists]]></title>
    <link href="https://stefan.vanburen.xyz/til/jq-key-exists/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/til/jq-key-exists/</id>
    <published>2021-04-22T11:11:03-04:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;m constantly using <a href="https://stedolan.github.io/jq/"><code>jq</code></a> to deal with <abbr title="JavaScript Object Notation">JSON</abbr> via the <abbr title="Command-Line Interface">CLI</abbr>.</p>
<p>Today I needed to figure out the difference between a key existing with a <code>null</code> value or not existing at all in a bit of <abbr title="JavaScript Object Notation">JSON</abbr>.</p>
<p>By default, &ldquo;querying&rdquo; a key via <code>jq</code> will return <code>null</code> whether the key exists and is <code>null</code>, <strong>or</strong> the key doesn&rsquo;t exist:</p>
<pre><code class="language-fish">△ # NOTE: the following is in fish, but should be straightforward to port to other shells

△ set json '{&quot;test&quot;: null}'

△ echo $json | jq .test
null

△ echo $json | jq .nonexistent
null
</code></pre>
<p>Instead, you can use the <code>jq</code>&rsquo;s <a href="https://stedolan.github.io/jq/manual/#has(key)"><code>has</code></a> function to determine the difference:</p>
<pre><code class="language-fish">△ echo $json | jq 'has(&quot;test&quot;)'
true

△ echo $json | jq 'has(&quot;nonexistent&quot;)'
false
</code></pre>
]]></content>
    <source:markdown><![CDATA[
I'm constantly using [`jq`](https://stedolan.github.io/jq/) to deal with <abbr title="JavaScript Object Notation">JSON</abbr> via the <abbr title="Command-Line Interface">CLI</abbr>.

Today I needed to figure out the difference between a key existing with a `null` value or not existing at all in a bit of <abbr title="JavaScript Object Notation">JSON</abbr>.

By default, "querying" a key via `jq` will return `null` whether the key exists and is `null`, **or** the key doesn't exist:

```fish
△ # NOTE: the following is in fish, but should be straightforward to port to other shells

△ set json '{"test": null}'

△ echo $json | jq .test
null

△ echo $json | jq .nonexistent
null
```

Instead, you can use the `jq`'s [`has`](https://stedolan.github.io/jq/manual/#has(key)) function to determine the difference:

```fish
△ echo $json | jq 'has("test")'
true

△ echo $json | jq 'has("nonexistent")'
false
```
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[New Domain]]></title>
    <link href="https://stefan.vanburen.xyz/blog/new-domain/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/new-domain/</id>
    <published>2021-04-16T21:07:47-04:00</published>
    <updated>2026-04-16T19:36:49-04:00</updated>
    <content type="html"><![CDATA[<p>A quick domain update:</p>
<ul>
<li>svbn.me -&gt; stefan.vanburen.xyz</li>
</ul>
<p>and to match, an email address update:</p>
<ul>
<li><a href="mailto:me@svbn.me">me@svbn.me</a> -&gt; <a href="mailto:stefan@vanburen.xyz">stefan@vanburen.xyz</a></li>
</ul>
<p>Will hold on to the old domain for awhile, so it should still work, but most things are being redirected.</p>
]]></content>
    <source:markdown><![CDATA[
A quick domain update:

* svbn.me -> stefan.vanburen.xyz

and to match, an email address update:

* <me@svbn.me> -> <stefan@vanburen.xyz>

Will hold on to the old domain for awhile, so it should still work, but most things are being redirected.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Hiccup Makes Writing HTML Fun]]></title>
    <link href="https://stefan.vanburen.xyz/blog/hiccup-makes-writing-html-fun/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/hiccup-makes-writing-html-fun/</id>
    <published>2021-03-21T19:48:36-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>At $work, I&rsquo;ve been writing a web application that is server-side rendered via Go&rsquo;s <code>html/template</code> package.</p>
<p>I&rsquo;ve been writing a toy application in Clojure with a similar approach: server rendered <abbr title="HyperText Markup Language">HTML</abbr> with <a href="https://tailwindcss.com">Tailwind</a> for styling and <a href="https://github.com/alpinejs/alpine">AlpineJS</a> for a hint of interactivity.</p>
<p>With the introduction of Go&rsquo;s new <code>embed</code> package in 1.16, I was tempted to port the application to Go.
I was imagining being able to embed the templates and assets in a static binary.</p>
<p>And I could still do that!
But I hesitated as soon as I reached the portion of rewriting my templates in Go, and for good reason: Go&rsquo;s template syntax isn&rsquo;t great, and Clojure has Hiccup.</p>
<h2 id="hiccup">
  Hiccup <a class="anchor" href="#hiccup">#</a>
</h2>
<p><a href="https://github.com/weavejester/hiccup">Hiccup</a> is a library for writing <abbr title="HyperText Markup Language">HTML</abbr> in Clojure.
Writing <abbr title="HyperText Markup Language">HTML</abbr> in Hiccup is a <em>dream</em>:</p>
<ul>
<li>Unlike <abbr title="HyperText Markup Language">HTML</abbr>, Hiccup automatically closes tags.</li>
<li>Hiccup doesn&rsquo;t require that you remember the proper way to close an <abbr title="HyperText Markup Language">HTML</abbr> element.</li>
<li>Hiccup has shortcuts for adding an <code>id</code> or <code>class</code> attributes.</li>
</ul>
<p>And, more than anything, Hiccup is written <strong>within</strong> your regular Clojure functions, which makes it incredibly easy to interweave logic with your templates.</p>
<p>A <code>&lt;form&gt;</code> in Hiccup might look like this:</p>
<pre><code class="language-clojure">[:form {:action &quot;/add-address&quot;
        :method &quot;POST&quot;}
  [:label {:for &quot;label-input&quot;} &quot;Label&quot;]
  [:input {:type &quot;text&quot;
           :id &quot;label-input&quot;
           :name &quot;label&quot;
           ;; for now, label is required
           :required true}]

  [:label {:for &quot;street-input&quot;} &quot;Street&quot;]
  [:input {:type &quot;text&quot;
           :id &quot;street-input&quot;
           :name &quot;street&quot;}]

  [:label {:for &quot;city-input&quot;} &quot;City&quot;]
  [:input {:type &quot;text&quot;
           :id &quot;city-input&quot;
           :name &quot;city&quot;}]

  [:label {:for &quot;state-input&quot;} &quot;State&quot;]
  [:input {:type &quot;text&quot;
           :id &quot;state-input&quot;
           :name &quot;state&quot;}]]
;; …
</code></pre>
<p>The beauty begins when you start to weave in the logic:</p>
<pre><code class="language-clojure">(def disabled-classes &quot;text-gray-500 …&quot;)

(defn button
  [id classes disabled?]
  [:button {:id id
            :class (if disabled?
                     (merge classes disabled-classes)
                     classes)
            :disabled disabled?}])
</code></pre>
<p>In this way, Hiccup is similar to the <abbr title="JavaScript XML">JSX</abbr> syntax while writing React — and this approach has been taken to the ClojureScript side via the <a href="https://github.com/reagent-project/reagent">Reagent</a> library.
(A more thorough example of Reagent-style Hiccup code can be found in my <a href="https://github.com/stefanvanburen/seven-guis/blob/main/src/app/main.cljs">seven-guis</a> project).</p>
<p>And, when you pair Hiccup with <a href="http://shaunlebron.github.io/parinfer/">Parinfer</a> + <a href="https://github.com/Olical/conjure">Conjure</a>, the <abbr title="Developer Experience">DX</abbr> is too good to pass up.
While I love writing Go, the conciseness, simplicity and the <abbr title="Read-Eval-Print Loop">REPL</abbr> of Clojure have drawn me in.
It&rsquo;s magical.</p>
<p>🪄</p>
]]></content>
    <source:markdown><![CDATA[
At $work, I've been writing a web application that is server-side rendered via Go's `html/template` package.

I've been writing a toy application in Clojure with a similar approach: server rendered <abbr title="HyperText Markup Language">HTML</abbr> with [Tailwind](https://tailwindcss.com) for styling and [AlpineJS](https://github.com/alpinejs/alpine) for a hint of interactivity.

With the introduction of Go's new `embed` package in 1.16, I was tempted to port the application to Go.
I was imagining being able to embed the templates and assets in a static binary.

And I could still do that!
But I hesitated as soon as I reached the portion of rewriting my templates in Go, and for good reason: Go's template syntax isn't great, and Clojure has Hiccup.

## Hiccup

[Hiccup](https://github.com/weavejester/hiccup) is a library for writing <abbr title="HyperText Markup Language">HTML</abbr> in Clojure.
Writing <abbr title="HyperText Markup Language">HTML</abbr> in Hiccup is a *dream*:

* Unlike <abbr title="HyperText Markup Language">HTML</abbr>, Hiccup automatically closes tags.
* Hiccup doesn't require that you remember the proper way to close an <abbr title="HyperText Markup Language">HTML</abbr> element.
* Hiccup has shortcuts for adding an `id` or `class` attributes.

And, more than anything, Hiccup is written **within** your regular Clojure functions, which makes it incredibly easy to interweave logic with your templates.

A `<form>` in Hiccup might look like this:

```clojure
[:form {:action "/add-address"
        :method "POST"}
  [:label {:for "label-input"} "Label"]
  [:input {:type "text"
           :id "label-input"
           :name "label"
           ;; for now, label is required
           :required true}]

  [:label {:for "street-input"} "Street"]
  [:input {:type "text"
           :id "street-input"
           :name "street"}]

  [:label {:for "city-input"} "City"]
  [:input {:type "text"
           :id "city-input"
           :name "city"}]

  [:label {:for "state-input"} "State"]
  [:input {:type "text"
           :id "state-input"
           :name "state"}]]
;; …
```

The beauty begins when you start to weave in the logic:

```clojure
(def disabled-classes "text-gray-500 …")

(defn button
  [id classes disabled?]
  [:button {:id id
            :class (if disabled?
                     (merge classes disabled-classes)
                     classes)
            :disabled disabled?}])
```

In this way, Hiccup is similar to the <abbr title="JavaScript XML">JSX</abbr> syntax while writing React — and this approach has been taken to the ClojureScript side via the [Reagent](https://github.com/reagent-project/reagent) library.
(A more thorough example of Reagent-style Hiccup code can be found in my [seven-guis](https://github.com/stefanvanburen/seven-guis/blob/main/src/app/main.cljs) project).

And, when you pair Hiccup with [Parinfer](http://shaunlebron.github.io/parinfer/) + [Conjure](https://github.com/Olical/conjure), the <abbr title="Developer Experience">DX</abbr> is too good to pass up.
While I love writing Go, the conciseness, simplicity and the <abbr title="Read-Eval-Print Loop">REPL</abbr> of Clojure have drawn me in.
It's magical.

🪄
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[<code>font-variant-numeric</code>]]></title>
    <link href="https://stefan.vanburen.xyz/blog/font-variant-numeric/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/font-variant-numeric/</id>
    <published>2021-01-24T12:55:27-05:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>I was reminded of the <code>font-variant-numeric</code> <abbr title="Cascading Style Sheets">CSS</abbr> property by <a href="https://twitter.com/jimniels/status/1353081335347351552">Jim Nielsen, here</a>, and ended up <a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz/commit/d6272469c9caf6b2286fa65cdef385f10e258642">using it in a similar way</a> for the dates on my <a href="/blog">list of blog posts</a>.</p>
<p>For context, without tabular-nums enabled, numbers on this site look like this:</p>
<p style="font-size: var(--size-700)">
0123456789
</p>
<p>And with it enabled, they look like this:</p>
<p style="font-size: var(--size-700)" class="nums">
0123456789
</p>
<p>At least on Apple platforms, where the system sans-serif font is <a href="https://developer.apple.com/fonts/">San Francisco</a>, the &ldquo;0&rdquo; and &ldquo;1&rdquo; characters have a little additional spacing, making them equal size to the rest of the digits.</p>
<p>Glad to get this changed &mdash; the previous look was somewhat off-putting, now that I look at it in comparison.
There are so many neat <abbr title="Cascading Style Sheets">CSS</abbr> properties nowadays for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant">all sorts of typographical situations</a>.</p>
]]></content>
    <source:markdown><![CDATA[
I was reminded of the `font-variant-numeric` <abbr title="Cascading Style Sheets">CSS</abbr> property by [Jim Nielsen, here](https://twitter.com/jimniels/status/1353081335347351552), and ended up [using it in a similar way](https://git.sr.ht/~svbn/stefan.vanburen.xyz/commit/d6272469c9caf6b2286fa65cdef385f10e258642) for the dates on my [list of blog posts](/blog).

For context, without tabular-nums enabled, numbers on this site look like this:

<p style="font-size: var(--size-700)">
0123456789
</p>

And with it enabled, they look like this:

<p style="font-size: var(--size-700)" class="nums">
0123456789
</p>

At least on Apple platforms, where the system sans-serif font is [San Francisco](https://developer.apple.com/fonts/), the "0" and "1" characters have a little additional spacing, making them equal size to the rest of the digits.

Glad to get this changed --- the previous look was somewhat off-putting, now that I look at it in comparison.
There are so many neat <abbr title="Cascading Style Sheets">CSS</abbr> properties nowadays for [all sorts of typographical situations](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant).
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Misusing go&rsquo;s <code>fmt.Sscanf</code>]]></title>
    <link href="https://stefan.vanburen.xyz/blog/misusing-go-fmt.sccanf/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/misusing-go-fmt.sccanf/</id>
    <published>2020-11-09T08:12:09-05:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Recently I was building a new website in go at $work and needed to do some <abbr title="Uniform Resource Locator">URL</abbr> parsing to grab some expected parameters in the <abbr title="Uniform Resource Locator">URL</abbr>.
The <abbr title="Uniform Resource Locator">URL</abbr> was expected to look something like the following: <code>/path/:id1/:id2</code>, where I was trying to grab <code>id1</code> and <code>id2</code> out of the <abbr title="Uniform Resource Locator">URL</abbr>.</p>
<p>In the past, I&rsquo;ve done something along the lines of:</p>
<pre><code class="language-go">splitPath := strings.Split(r.URL.Path, &quot;/&quot;)

// error handling, etc.

// splitPath is [&quot;&quot;, &quot;path&quot;, &quot;:id1&quot;, &quot;:id2&quot;],
// because of the way `strings.Split` works
id1 := splitPath[2]
id2 := splitPath[3]
</code></pre>
<p>However, I had come across the following code, from <a href="https://git.sr.ht/~sircmpwn/builds.sr.ht/tree/master/worker/http.go#L20">the source code of builds.sr.ht</a>:</p>
<pre><code class="language-go">var (
	jobId int
	op    string
)
_, err := fmt.Sscanf(r.URL.Path, &quot;/job/%d/%s&quot;, &amp;jobId, &amp;op)
</code></pre>
<p><code>fmt.Sscanf</code>? Haven&rsquo;t seen that in use often! But it seemed like a great fit for the exact problem I was solving.</p>
<p>So, I tried it:</p>
<pre><code class="language-go">var id1, id2 string
matchCount, err := fmt.Sscanf(r.URL.Path, &quot;/path/%s/%s&quot;, &amp;id1, &amp;id2)
if err != nil {
	fmt.Printf(&quot;err: %s, match count: %d\n&quot;, err, matchCount)
	fmt.Printf(&quot;id1: %s, id2: %s&quot;, id1, id2)
} else {
	fmt.Printf(&quot;no err, id1: %s, id2: %s&quot;, id1, id2)
}
</code></pre>
<p>Here&rsquo;s a <a href="https://play.golang.org/p/I3AtQCF_Wvv">slightly modified version on play.golang.org</a>.</p>
<details>
  <summary>Spoiler: here's the output.</summary>
  <samp>
  err: unexpected EOF, match count: 1<br>
  id1: abc/def, id2:
  </samp>
</details>
<hr>
<p>So, the first match hit by the scan, because it&rsquo;s <code>%s</code>, continues through the <code>/</code> character and picks up <code>id2</code>&rsquo;s value as well, leaving nothing for <code>id2</code>.</p>
<p>So why did it work in the line of code shown above?</p>
<p>Because the <code>%d</code> format specifier only captures numeric characters, so the <code>/</code> character breaks up the scan.</p>
<p>I messed around with this approach for awhile, but ultimately gave up and went back to my approach using <code>strings.Split</code>.
But, it&rsquo;s a nice reminder that format strings in go can be used both for formatting output and for scanning input, even if there are some footguns attached.</p>
<p>🔫</p>
]]></content>
    <source:markdown><![CDATA[
Recently I was building a new website in go at $work and needed to do some <abbr title="Uniform Resource Locator">URL</abbr> parsing to grab some expected parameters in the <abbr title="Uniform Resource Locator">URL</abbr>.
The <abbr title="Uniform Resource Locator">URL</abbr> was expected to look something like the following: `/path/:id1/:id2`, where I was trying to grab `id1` and `id2` out of the <abbr title="Uniform Resource Locator">URL</abbr>.

In the past, I've done something along the lines of:

```go
splitPath := strings.Split(r.URL.Path, "/")

// error handling, etc.

// splitPath is ["", "path", ":id1", ":id2"],
// because of the way `strings.Split` works
id1 := splitPath[2]
id2 := splitPath[3]
```

However, I had come across the following code, from [the source code of builds.sr.ht](https://git.sr.ht/~sircmpwn/builds.sr.ht/tree/master/worker/http.go#L20):

```go
var (
	jobId int
	op    string
)
_, err := fmt.Sscanf(r.URL.Path, "/job/%d/%s", &jobId, &op)
```

`fmt.Sscanf`? Haven't seen that in use often! But it seemed like a great fit for the exact problem I was solving.

So, I tried it:

```go
var id1, id2 string
matchCount, err := fmt.Sscanf(r.URL.Path, "/path/%s/%s", &id1, &id2)
if err != nil {
	fmt.Printf("err: %s, match count: %d\n", err, matchCount)
	fmt.Printf("id1: %s, id2: %s", id1, id2)
} else {
	fmt.Printf("no err, id1: %s, id2: %s", id1, id2)
}
```

Here's a [slightly modified version on play.golang.org](https://play.golang.org/p/I3AtQCF_Wvv).

<details>
  <summary>Spoiler: here's the output.</summary>
  <samp>
  err: unexpected EOF, match count: 1<br>
  id1: abc/def, id2:
  </samp>
</details>

* * *

So, the first match hit by the scan, because it's `%s`, continues through the `/` character and picks up `id2`'s value as well, leaving nothing for `id2`.

So why did it work in the line of code shown above?

Because the `%d` format specifier only captures numeric characters, so the `/` character breaks up the scan.

I messed around with this approach for awhile, but ultimately gave up and went back to my approach using `strings.Split`.
But, it's a nice reminder that format strings in go can be used both for formatting output and for scanning input, even if there are some footguns attached.

🔫
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[rams.vim]]></title>
    <link href="https://stefan.vanburen.xyz/blog/rams.vim/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/rams.vim/</id>
    <published>2020-09-15T16:38:18-04:00</published>
    <updated>2024-04-20T16:02:22-04:00</updated>
    <content type="html"><![CDATA[<blockquote>
<p>A quick update: I&rsquo;ve <a href="https://github.com/stefanvanburen/rams.vim/commit/7c7519489f552f37ca1f35011b220c38e0587103">put the repository in maintenance mode</a>,
and started work on a <a href="https://github.com/stefanvanburen/rams">neovim colorscheme</a> based on the same colors.
The end goal is to have a generalized colorscheme that can be exported for use elsewhere.</p>
</blockquote>
<p>I published the vim colorscheme I&rsquo;ve been using for the last few months <a href="https://github.com/stefanvanburen/rams.vim">publicly on GitHub</a>.
It&rsquo;s called rams.vim, after the German Designer, <a href="https://en.wikipedia.org/wiki/Dieter_Rams">Dieter Rams</a>.</p>
<p>I had the idea for the colorscheme after watching <a href="https://en.wikipedia.org/wiki/Gary_Hustwit">Gary Hustwit&rsquo;s</a> <a href="https://en.wikipedia.org/wiki/Rams_(2018_film)"><em>Rams</em></a>.
It&rsquo;s simple: off-black, off-white, a grey and an accent color (a medium-bright red).
I also added a green and red that are specifically for showing diffs in vim.</p>
<p>The colorscheme is generated via <a href="https://github.com/lifepillar/vim-colortemplate">vim-colortemplate</a>, which aided the creation greatly &mdash; I have no previous experience with vim colorscheme creation, and based on the other colorschemes I surveyed, it seemed like one of the few sane ways to get an idea up and running quickly.</p>
<p>I&rsquo;m quite happy with the colorscheme as is &mdash; I&rsquo;ve been using the light colors for a couple months now.
The dark mode could use some work, but I&rsquo;m not using it regularly, so I&rsquo;ve been mostly keeping it in sync with the light variant.
I&rsquo;ve also added some plugin-specific highlights for the obvious candidates (<a href="https://github.com/dense-analysis/ale">ALE</a>, <a href="https://github.com/tpope/vim-fugitive">fugitive</a> / <a href="https://github.com/airblade/vim-gitgutter">gitgutter</a>), but haven&rsquo;t explored all of the plugins I kept around after my <a href="/blog/spring-cleaning">dotfile purge</a>.</p>
<p>If you&rsquo;re a vim user, give it a try, and let me know what you think!</p>
]]></content>
    <source:markdown><![CDATA[
> A quick update: I've [put the repository in maintenance mode](https://github.com/stefanvanburen/rams.vim/commit/7c7519489f552f37ca1f35011b220c38e0587103),
> and started work on a [neovim colorscheme](https://github.com/stefanvanburen/rams) based on the same colors.
> The end goal is to have a generalized colorscheme that can be exported for use elsewhere.

I published the vim colorscheme I've been using for the last few months [publicly on GitHub](https://github.com/stefanvanburen/rams.vim).
It's called rams.vim, after the German Designer, [Dieter Rams](https://en.wikipedia.org/wiki/Dieter_Rams).

I had the idea for the colorscheme after watching [Gary Hustwit's](https://en.wikipedia.org/wiki/Gary_Hustwit) [_Rams_](https://en.wikipedia.org/wiki/Rams_(2018_film)).
It's simple: off-black, off-white, a grey and an accent color (a medium-bright red).
I also added a green and red that are specifically for showing diffs in vim.

The colorscheme is generated via [vim-colortemplate](https://github.com/lifepillar/vim-colortemplate), which aided the creation greatly --- I have no previous experience with vim colorscheme creation, and based on the other colorschemes I surveyed, it seemed like one of the few sane ways to get an idea up and running quickly.

I'm quite happy with the colorscheme as is --- I've been using the light colors for a couple months now.
The dark mode could use some work, but I'm not using it regularly, so I've been mostly keeping it in sync with the light variant.
I've also added some plugin-specific highlights for the obvious candidates ([ALE](https://github.com/dense-analysis/ale), [fugitive](https://github.com/tpope/vim-fugitive) / [gitgutter](https://github.com/airblade/vim-gitgutter)), but haven't explored all of the plugins I kept around after my [dotfile purge](/blog/spring-cleaning).

If you're a vim user, give it a try, and let me know what you think!
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[small]]></title>
    <link href="https://stefan.vanburen.xyz/blog/small/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/small/</id>
    <published>2020-07-29T19:24:11-04:00</published>
    <updated>2023-01-28T21:51:19-05:00</updated>
    <content type="html"><![CDATA[<p>I was browsing through my GitHub repositories the other day and noticed <code>small</code>, a CLI tool I started working on about two years ago to convert text.
I finally polished up the repo and <a href="https://github.com/stefanvanburen/small">published it for all to see</a>.</p>
<p>It&rsquo;s really not much, but it gave me a chance to think about the expected behaviors associated with a tool like <code>small</code>.
In particular, support for being piped (<code>$ command | small</code>) is a critical bit of playing nice with other UNIX-y tools.</p>
<p>Anyways, it was fun getting it out there.</p>
<pre><code class="language-console">$ small -h
USAGE
  small [FLAGS] [TEXT...]
  command | small [FLAGS]

FLAGS
  -h         print this help message
  -l         list transform types
  -t=TYPE    specify transform type

$ small &quot;here's some text!&quot;
ʜᴇʀᴇ'ꜱ ꜱᴏᴍᴇ ᴛᴇxᴛ!

</code></pre>
<p>🤖</p>
]]></content>
    <source:markdown><![CDATA[
I was browsing through my GitHub repositories the other day and noticed `small`, a CLI tool I started working on about two years ago to convert text.
I finally polished up the repo and [published it for all to see](https://github.com/stefanvanburen/small).

It's really not much, but it gave me a chance to think about the expected behaviors associated with a tool like `small`.
In particular, support for being piped (`$ command | small`) is a critical bit of playing nice with other UNIX-y tools.

Anyways, it was fun getting it out there.

```console
$ small -h
USAGE
  small [FLAGS] [TEXT...]
  command | small [FLAGS]

FLAGS
  -h         print this help message
  -l         list transform types
  -t=TYPE    specify transform type

$ small "here's some text!"
ʜᴇʀᴇ'ꜱ ꜱᴏᴍᴇ ᴛᴇxᴛ!

```

🤖
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[prutsen]]></title>
    <link href="https://stefan.vanburen.xyz/blog/prutsen/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/prutsen/</id>
    <published>2020-06-11T21:24:16-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>I recently learned of &ldquo;prutsen&rdquo;, a Dutch word meaning <a href="https://en.wiktionary.org/wiki/prutsen">&ldquo;to fiddle around&rdquo;</a>.
In a lot of ways, I think it encompasses a lot of how I approach some of my computer diversions.</p>
<p>For example, this website, at the time of writing, has <a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz">135 commits on it</a>.
With a total of five published posts, and most of my posts being written within a commit or two, much of the work being done on this site is cosmetic.
For example, <a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz/commit/6107cf3e0cbea2313179880abab3fd3050693a12">tweaking <abbr title="Cascading Style Sheets">CSS</abbr> indentation</a> or <a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz/commit/12330139fc35f3d5289910bc9367b5abee8b5e12">ensuring the RSS feed contains the entire content of the post</a>.</p>
<p>Likewise, my dotfiles sit at a <a href="https://github.com/stefanvanburen/dotfiles">healthy 910 commits</a> currently.
I recently went through a bit of a <a href="/blog/spring-cleaning">dotfile purge</a> that added quite a few of those, but in general, tweaking my dotfiles has been a hobby for the better part of a decade.</p>
<p>Prutsen gives me a name for all this tweaking and fiddling &mdash; so cathartic!</p>
<p>💆</p>
<p><em>Update</em>: After this post I truly could not remember where I had found &ldquo;prutsen&rdquo; in the first place.
I finally found it: <a href="https://thedobook.co/products/do-inhabit-style-your-space-for-a-creative-and-considered-life">Do Inhabit</a>, by Sue Fan and Danielle Quigley.</p>
<p>From the book:</p>
<figure>
  <blockquote>
    <p>
    This Dutch word translates loosely to doing something of very little significance that only looks like work, or tinkering.
    </p>
  </blockquote>
  <figcaption>
    Page 11, <cite>Do Inhabit</cite>
  </figcaption>
</figure>
<p>Good to know where it originally came from!</p>
]]></content>
    <source:markdown><![CDATA[
I recently learned of "prutsen", a Dutch word meaning ["to fiddle around"](https://en.wiktionary.org/wiki/prutsen).
In a lot of ways, I think it encompasses a lot of how I approach some of my computer diversions.

For example, this website, at the time of writing, has [135 commits on it](https://git.sr.ht/~svbn/stefan.vanburen.xyz).
With a total of five published posts, and most of my posts being written within a commit or two, much of the work being done on this site is cosmetic.
For example, <a href="https://git.sr.ht/~svbn/stefan.vanburen.xyz/commit/6107cf3e0cbea2313179880abab3fd3050693a12">tweaking <abbr title="Cascading Style Sheets">CSS</abbr> indentation</a> or [ensuring the RSS feed contains the entire content of the post](https://git.sr.ht/~svbn/stefan.vanburen.xyz/commit/12330139fc35f3d5289910bc9367b5abee8b5e12).

Likewise, my dotfiles sit at a [healthy 910 commits](https://github.com/stefanvanburen/dotfiles) currently.
I recently went through a bit of a [dotfile purge](/blog/spring-cleaning) that added quite a few of those, but in general, tweaking my dotfiles has been a hobby for the better part of a decade.

Prutsen gives me a name for all this tweaking and fiddling --- so cathartic!

💆

_Update_: After this post I truly could not remember where I had found "prutsen" in the first place.
I finally found it: [Do Inhabit](https://thedobook.co/products/do-inhabit-style-your-space-for-a-creative-and-considered-life), by Sue Fan and Danielle Quigley.

From the book:

<figure>
  <blockquote>
    <p>
    This Dutch word translates loosely to doing something of very little significance that only looks like work, or tinkering.
    </p>
  </blockquote>
  <figcaption>
    Page 11, <cite>Do Inhabit</cite>
  </figcaption>
</figure>

Good to know where it originally came from!
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[<code>git pull --autostash</code>]]></title>
    <link href="https://stefan.vanburen.xyz/blog/git-pull-autostash/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/git-pull-autostash/</id>
    <published>2020-05-13T19:11:09-04:00</published>
    <updated>2026-05-11T20:01:13-04:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;ve recently moved from a largely merge commit based git workflow to a squash and merge based one.
Learning to flex my <code>git rebase</code> muscles has been refreshing, and I&rsquo;m enjoying the cleaner <code>git log</code> that results.</p>
<p>As part of the workflow, I&rsquo;ve changed my <code>git pull</code> to <a href="https://github.com/stefanvanburen/dotfiles/commit/de0f57867ba3270212c02884ec1053e64158fa1b">default to rebasing on pull</a>, rather than merging (the default).
The only thing that&rsquo;s rough about this workflow is whenever I have local changes, <code>git pull</code> will fail, telling me that I have unstaged changes:</p>
<pre><code class="language-console">$ git pull
error: cannot pull with rebase: You have unstaged changes.
error: please commit or stash them.
</code></pre>
<p>To work around this, I&rsquo;d typically do a compound shell command (I&rsquo;m using <code>fish</code> &mdash; you would typically do this with <code>&amp;&amp;</code> in <code>bash</code>):</p>
<pre><code class="language-console">$ git stash; and git pull; and git stash pop
Saved working directory and index state WIP on main: 79911a7 chore: Remove gemini
Already up to date.
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use &quot;git add &lt;file&gt;...&quot; to update what will be committed)
  (use &quot;git restore &lt;file&gt;...&quot; to discard changes in working directory)
        modified:   content/blog/git-pull-autostash.md
        modified:   content/blog/small.md

no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;)
Dropped refs/stash@{0} (2ecce561c9bbd9cf222848d4b225a49edb816bb2)
</code></pre>
<p>I recently discovered the solution to needing this: <code>git pull --autostash</code>!
It automatically stashes your current working directory and re-applies it after the pull.</p>
<pre><code class="language-console">$ git pull --autostash
Created autostash: a14af18
Current branch master is up to date.
Applied autostash.
</code></pre>
<p>This option is so handy for my workflow that I <a href="https://github.com/stefanvanburen/dotfiles/commit/297733">made an alias for it</a>.</p>
<p>🥳</p>
]]></content>
    <source:markdown><![CDATA[
I've recently moved from a largely merge commit based git workflow to a squash and merge based one.
Learning to flex my `git rebase` muscles has been refreshing, and I'm enjoying the cleaner `git log` that results.

As part of the workflow, I've changed my `git pull` to [default to rebasing on pull](https://github.com/stefanvanburen/dotfiles/commit/de0f57867ba3270212c02884ec1053e64158fa1b), rather than merging (the default).
The only thing that's rough about this workflow is whenever I have local changes, `git pull` will fail, telling me that I have unstaged changes:

```console
$ git pull
error: cannot pull with rebase: You have unstaged changes.
error: please commit or stash them.
```

To work around this, I'd typically do a compound shell command (I'm using `fish` --- you would typically do this with `&&` in `bash`):

```console
$ git stash; and git pull; and git stash pop
Saved working directory and index state WIP on main: 79911a7 chore: Remove gemini
Already up to date.
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   content/blog/git-pull-autostash.md
        modified:   content/blog/small.md

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (2ecce561c9bbd9cf222848d4b225a49edb816bb2)
```

I recently discovered the solution to needing this: `git pull --autostash`!
It automatically stashes your current working directory and re-applies it after the pull.

```console
$ git pull --autostash
Created autostash: a14af18
Current branch master is up to date.
Applied autostash.
```

This option is so handy for my workflow that I [made an alias for it](https://github.com/stefanvanburen/dotfiles/commit/297733).

🥳
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Spring Cleaning]]></title>
    <link href="https://stefan.vanburen.xyz/blog/spring-cleaning/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/spring-cleaning/</id>
    <published>2020-04-18T08:11:33-04:00</published>
    <updated>2026-04-23T06:44:40-04:00</updated>
    <content type="html"><![CDATA[<p>Spring is in the air!
Inspired by <a href="https://deletionday.com/">Deletion Day</a>, I&rsquo;ve been tidying up some of my physical and digital life.
I&rsquo;ve deleted old accounts, removed applications that I&rsquo;m not using, and gotten rid of extraneous hardware.
But, the cleanup I&rsquo;m most proud of is my <a href="https://github.com/stefanvanburen/dotfiles">dotfiles</a> cleanse.</p>
<h2 id="the-purge">
  the purge <a class="anchor" href="#the-purge">#</a>
</h2>
<p>For awhile, I was a bit of a dotfiles packrat.
I worried that the knowledge I had gained &mdash; the choices I had made &mdash; would be lost to the depths of time if I were to remove them from the text of the files themselves.
That meant old color schemes, old key bindings, old tips and tricks, all stayed in their respective places.
Perhaps moved around, but not deleted.
Over the years, this meant that my <a href="https://github.com/stefanvanburen/dotfiles/blob/4d1f7f/vimrc">vimrc had bloated up to ~1600 lines</a>!
I had also accumulated a lot of files for tools I&rsquo;m no longer using, that were strewn all over my system.</p>
<p>I finally reached a breaking point, <a href="https://github.com/stefanvanburen/dotfiles/commit/9ed3e0">removing the <code>cheat</code> tool</a>.
There was nothing wrong with it, but I wasn&rsquo;t using it as often as I thought.
I used this as momentum to move on to my <a href="https://github.com/stefanvanburen/dotfiles/commits/main?after=f56b7bf67301f60785f7a7af1ced945d25b4d5c8+174">other</a> <a href="https://github.com/stefanvanburen/dotfiles/commits/main?before=f56b7bf67301f60785f7a7af1ced945d25b4d5c8+175">configuration</a> <a href="https://github.com/stefanvanburen/dotfiles/commits/main?before=f56b7bf67301f60785f7a7af1ced945d25b4d5c8+140">files</a>, mostly my vimrc.
Previously, I had been fairly lax on my git hygiene for the repo, using commit messages like <a href="https://github.com/stefanvanburen/dotfiles/commit/26852a">&ldquo;Various updates&rdquo;</a> or simply a <a href="https://github.com/stefanvanburen/dotfiles/commit/579368">&quot;✨&quot;</a>.
I knew that removing all that accumulated knowledge meant that I needed to have better practices, so I started using a <code>tool:</code> prefix on my commit subjects, and trying to be better about abiding to <a href="https://en.wikipedia.org/wiki/Atomic_commit#Atomic_commit_convention">atomic commits</a>.</p>
<h2 id="neovim">
  (neo)vim <a class="anchor" href="#neovim">#</a>
</h2>
<p><del>Now, my <a href="https://github.com/stefanvanburen/dotfiles/commits/main/vim/init.vim">vimrc</a> is a lean ~350 lines!</del>
<ins>Update (August 2022): I&rsquo;m now fully in on the Lua (actually, Fennel) neovim configuration, so the previous link is invalid.</ins>
(Maybe not lean for a vanilla vim purist, but it&rsquo;s reasonable for me!)
You&rsquo;ll also notice that I&rsquo;ve converted over to neovim fully.
I had a number of settings, plugins, and mappings that were working around some of vim&rsquo;s weirdness, and I was using neovim 98% of the time.
I removed all the settings that were the default in neovim and did a lot of <code>:help</code> diving to understand the settings that remained.
I also dropped plugins that I used sparingly, some of the filetype specific autocommands, and the sectioning comments used for folding.
I&rsquo;ve added a few plugins back that I wasn&rsquo;t quite able to shake (notably, I tried to learn vim&rsquo;s builtin autocomplete commands, but found <a href="https://github.com/Shougo/deoplete.nvim">deoplete</a> to be something I couldn&rsquo;t avoid using).</p>
<h2 id="">
  <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html"><abbr title="Cross-Desktop Group">XDG</abbr> Base Dir</a> <a class="anchor" href="#">#</a>
</h2>
<p>Switching over to neovim meant that I could remove the hacks around neovim finding vim&rsquo;s configuration directory, and move everything to ~/.config.
I could also leverage neovim&rsquo;s data dir for <a href="https://github.com/stefanvanburen/dotfiles/blob/f56b7bf67301f60785f7a7af1ced945d25b4d5c8/vim/init.vim#L24">storing my plugins</a>.
Given the neovim changes, I started to look elsewhere where I could move my dotfiles in ~/.config, and managed to get <a href="https://github.com/stefanvanburen/dotfiles/blob/f56b7bf67301f60785f7a7af1ced945d25b4d5c8/install.conf.yaml#L14-L23">everything except .tmux.conf</a> moved over.
This includes my global git configuration, which until now I didn&rsquo;t realize could be anywhere but ~/.gitconfig and ~/.gitignore!</p>
<h2 id="other-configuration">
  other configuration <a class="anchor" href="#other-configuration">#</a>
</h2>
<p>Alongside the effort to move everything to within ~/.config, I ended up removing language specific configuration (mypy, isort, and flake8 config for Python, profiles.clj for Clojure), tooling configuration (curlrc, editorconfig, vale.ini), old package listings (Brewfile), and unused shell configuration (zprofile, zshenv, and zshrc).
Overall, <a href="https://github.com/stefanvanburen/dotfiles/compare/4d1f7f...f56b7b">I&rsquo;ve reduced my dotfiles down to things that I use <em>now</em></a>.</p>
<p>And it feels great.</p>
<p>🛁</p>
]]></content>
    <source:markdown><![CDATA[
Spring is in the air!
Inspired by [Deletion Day](https://deletionday.com/), I've been tidying up some of my physical and digital life.
I've deleted old accounts, removed applications that I'm not using, and gotten rid of extraneous hardware.
But, the cleanup I'm most proud of is my [dotfiles](https://github.com/stefanvanburen/dotfiles) cleanse.

## the purge

For awhile, I was a bit of a dotfiles packrat.
I worried that the knowledge I had gained --- the choices I had made --- would be lost to the depths of time if I were to remove them from the text of the files themselves.
That meant old color schemes, old key bindings, old tips and tricks, all stayed in their respective places.
Perhaps moved around, but not deleted.
Over the years, this meant that my [vimrc had bloated up to ~1600 lines](https://github.com/stefanvanburen/dotfiles/blob/4d1f7f/vimrc)!
I had also accumulated a lot of files for tools I'm no longer using, that were strewn all over my system.

I finally reached a breaking point, [removing the `cheat` tool](https://github.com/stefanvanburen/dotfiles/commit/9ed3e0).
There was nothing wrong with it, but I wasn't using it as often as I thought.
I used this as momentum to move on to my [other](https://github.com/stefanvanburen/dotfiles/commits/main?after=f56b7bf67301f60785f7a7af1ced945d25b4d5c8+174) [configuration](https://github.com/stefanvanburen/dotfiles/commits/main?before=f56b7bf67301f60785f7a7af1ced945d25b4d5c8+175) [files](https://github.com/stefanvanburen/dotfiles/commits/main?before=f56b7bf67301f60785f7a7af1ced945d25b4d5c8+140), mostly my vimrc.
Previously, I had been fairly lax on my git hygiene for the repo, using commit messages like ["Various updates"](https://github.com/stefanvanburen/dotfiles/commit/26852a) or simply a ["✨"](https://github.com/stefanvanburen/dotfiles/commit/579368).
I knew that removing all that accumulated knowledge meant that I needed to have better practices, so I started using a `tool:` prefix on my commit subjects, and trying to be better about abiding to [atomic commits](https://en.wikipedia.org/wiki/Atomic_commit#Atomic_commit_convention).

## (neo)vim

<del>Now, my [vimrc](https://github.com/stefanvanburen/dotfiles/commits/main/vim/init.vim) is a lean ~350 lines!</del>
<ins>Update (August 2022): I'm now fully in on the Lua (actually, Fennel) neovim configuration, so the previous link is invalid.</ins>
(Maybe not lean for a vanilla vim purist, but it's reasonable for me!)
You'll also notice that I've converted over to neovim fully.
I had a number of settings, plugins, and mappings that were working around some of vim's weirdness, and I was using neovim 98% of the time.
I removed all the settings that were the default in neovim and did a lot of `:help` diving to understand the settings that remained.
I also dropped plugins that I used sparingly, some of the filetype specific autocommands, and the sectioning comments used for folding.
I've added a few plugins back that I wasn't quite able to shake (notably, I tried to learn vim's builtin autocomplete commands, but found [deoplete](https://github.com/Shougo/deoplete.nvim) to be something I couldn't avoid using).

## [<abbr title="Cross-Desktop Group">XDG</abbr> Base Dir](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)

Switching over to neovim meant that I could remove the hacks around neovim finding vim's configuration directory, and move everything to ~/.config.
I could also leverage neovim's data dir for [storing my plugins](https://github.com/stefanvanburen/dotfiles/blob/f56b7bf67301f60785f7a7af1ced945d25b4d5c8/vim/init.vim#L24).
Given the neovim changes, I started to look elsewhere where I could move my dotfiles in ~/.config, and managed to get [everything except .tmux.conf](https://github.com/stefanvanburen/dotfiles/blob/f56b7bf67301f60785f7a7af1ced945d25b4d5c8/install.conf.yaml#L14-L23) moved over.
This includes my global git configuration, which until now I didn't realize could be anywhere but ~/.gitconfig and ~/.gitignore!

## other configuration

Alongside the effort to move everything to within ~/.config, I ended up removing language specific configuration (mypy, isort, and flake8 config for Python, profiles.clj for Clojure), tooling configuration (curlrc, editorconfig, vale.ini), old package listings (Brewfile), and unused shell configuration (zprofile, zshenv, and zshrc).
Overall, [I've reduced my dotfiles down to things that I use _now_](https://github.com/stefanvanburen/dotfiles/compare/4d1f7f...f56b7b).

And it feels great.

🛁
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Book Review: Mr. Penumbra&rsquo;s 24-hour Bookstore]]></title>
    <link href="https://stefan.vanburen.xyz/blog/mr-penumbras-24-hour-bookstore-review/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/mr-penumbras-24-hour-bookstore-review/</id>
    <published>2019-12-26T20:40:50-05:00</published>
    <updated>2024-04-20T16:02:22-04:00</updated>
    <content type="html"><![CDATA[<p>Rating: 4/5 ⭐️</p>
<p><strong><em>Warning: Spoilers below</em></strong></p>
<p><a href="https://www.worldcat.org/title/mr-penumbras-24-hour-bookstore-a-novel/oclc/1310309732">Mr. Penumbra&rsquo;s 24-hour bookstore</a> is an amusing and intriguing tale concerning a new clerk at a strange bookstore who uncovers the mysteries of his new place of work.
Clay (the clerk and main protagonist) discovers a secret society that his employer is a part of, and seeks to bring the mystery to light.</p>
<p>I came into contact with the title via Robin Sloan&rsquo;s blog for 2019, <a href="https://desert.glass/">Year of the Meteor</a>, which I&rsquo;ve adored.
The assortment of links and life commentary has been wonderful all year, and I&rsquo;m looking forward to Sloan&rsquo;s next project.</p>
<p>Overall, the book flowed well and moved quickly; I&rsquo;m not a very fast reader and made it through in around 5 and a half hours total, largely over the course of two days.
The writing included a lot of references for a more technical crowd &mdash; or, at least, a crowd familiar with the Silicon Valley startup scene.
There were a handful of mentions of Google specific technologies, as well as allusions to computing history.</p>
<p>However, I felt the novel was lacking in the department of having a serious antagonist, and a bit too much feeling of Deus ex machina at the end.
The novel&rsquo;s antagonist, Corvina, had essentially no chance of stopping the protagonists from achieving their goal.
Meanwhile, when everything seemed lost after a failed attempt at code-breaking with the entirety of Google&rsquo;s compute power, and the rest of the novel being largely about the protagonist&rsquo;s ability to bring together his eclectic set of friends&rsquo; talents to bear on the mystery at hand, the story is resolved by Clay on his own.</p>
<p>A feel-good ending was somewhat ruined by the quickness of the last third of the book.
But still, I was entertained the entire way, and recommend giving it a read.</p>
]]></content>
    <source:markdown><![CDATA[
Rating: 4/5 ⭐️

**_Warning: Spoilers below_**

[Mr. Penumbra's 24-hour bookstore](https://www.worldcat.org/title/mr-penumbras-24-hour-bookstore-a-novel/oclc/1310309732) is an amusing and intriguing tale concerning a new clerk at a strange bookstore who uncovers the mysteries of his new place of work.
Clay (the clerk and main protagonist) discovers a secret society that his employer is a part of, and seeks to bring the mystery to light.

I came into contact with the title via Robin Sloan's blog for 2019, [Year of the Meteor](https://desert.glass/), which I've adored.
The assortment of links and life commentary has been wonderful all year, and I'm looking forward to Sloan's next project.

Overall, the book flowed well and moved quickly; I'm not a very fast reader and made it through in around 5 and a half hours total, largely over the course of two days.
The writing included a lot of references for a more technical crowd --- or, at least, a crowd familiar with the Silicon Valley startup scene.
There were a handful of mentions of Google specific technologies, as well as allusions to computing history.

However, I felt the novel was lacking in the department of having a serious antagonist, and a bit too much feeling of Deus ex machina at the end.
The novel's antagonist, Corvina, had essentially no chance of stopping the protagonists from achieving their goal.
Meanwhile, when everything seemed lost after a failed attempt at code-breaking with the entirety of Google's compute power, and the rest of the novel being largely about the protagonist's ability to bring together his eclectic set of friends' talents to bear on the mystery at hand, the story is resolved by Clay on his own.

A feel-good ending was somewhat ruined by the quickness of the last third of the book.
But still, I was entertained the entire way, and recommend giving it a read.
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[<code>extract</code> in fish]]></title>
    <link href="https://stefan.vanburen.xyz/blog/fish-extract/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/fish-extract/</id>
    <published>2019-11-14T10:40:55-05:00</published>
    <updated>2024-04-20T16:02:22-04:00</updated>
    <content type="html"><![CDATA[<p>I recently switched from <a href="https://en.wikipedia.org/wiki/Z_shell">zsh</a> to <a href="https://fishshell.com/">fish</a> as my shell of choice.
I liked the idea of starting from scratch, with the sane defaults that fish provides, as my <a href="https://github.com/stefanvanburen/dotfiles/commit/10d9acc84179425772597d5a4c34c70a8bddd906#diff-53cae0c7df819f6e6a8104beae0f53a1">zsh configuration files were getting a bit out of control</a>.</p>
<p>For the most part, the transition was fairly painless and straightforward.
However, I still miss a few of the zsh niceties that I had been used to over the years &mdash; one being <a href="https://github.com/robbyrussell/oh-my-zsh/blob/master/plugins/extract/extract.plugin.zsh"><code>extract</code> function provided in oh-my-zsh</a>.
Even after moving away from oh-my-zsh as my plugin manager in zsh, I had used my new manager to grab <a href="https://github.com/stefanvanburen/dotfiles/blob/74dd7a02b83ca1874d721e242e0f466ca1f65692/zshrc#L13-L14">just that plugin</a> for my usage.</p>
<p>After a cursory google, it seemed like a fish port of the plugin didn&rsquo;t exist, so I decided to try to port over the plugin myself.
You can find the whole function <a href="https://github.com/stefanvanburen/dotfiles/blob/9e62163c674f3fef58a12d752daa78b4c5eeecbe/config.fish#L65-L125">in my config.fish in my dotfiles</a>.</p>
<p>First, we&rsquo;ll define a function named <code>extract</code> and give it a description.
I&rsquo;ve also noted in a comment where this function was ported from.</p>
<pre><code class="language-fish">function extract -d &quot;extract files from archives&quot;
    # largely adapted from
    # https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract
</code></pre>
<p>The function first checks if we have arguments &mdash; if we have none, there&rsquo;s nothing to do, so we&rsquo;ll echo a usage string to stderr and exit.</p>
<pre><code class="language-fish">    # no arguments, write usage
    if test (count $argv) -eq 0
        echo &quot;Usage: extract [-option] [file ...] &quot; &gt;&amp;2
        echo &quot; Options:&quot; &gt;&amp;2
        echo &quot; -r, --remove    Remove archive after unpacking.&quot; &gt;&amp;2
        exit 1
    end
</code></pre>
<p>Next, we check to see if the <code>-r</code> / <code>--remove</code> option has been supplied as the first argument.
If it is, we&rsquo;ll remove the archive files after they&rsquo;ve been successfully unarchived.
We also remove this argument so that our main loop can iterate correctly over the filenames.</p>
<pre><code class="language-fish">    set remove_file 0
    if test $argv[1] = &quot;-r&quot;; or test $argv[1] = &quot;--remove&quot;
        set remove_file 1
        set --erase argv[1]
    end
</code></pre>
<p>Now comes the main loop, which iterates over the filenames supplied.</p>
<pre><code class="language-fish">    for i in $argv[1..-1]
</code></pre>
<p>First, we ensure that the filename arguments are valid files:</p>
<pre><code class="language-fish">        if test ! -f $i
            echo &quot;extract: '$i' is not a valid file&quot; &gt;&amp;2
            continue
        end
</code></pre>
<p>We default our success value to <code>0</code> &mdash; if the file&rsquo;s extension isn&rsquo;t something we can deal with, we&rsquo;ll set this to <code>1</code> so that we can avoid removing the file even if the remove option is set.
Then, we grab the extension via some fish regex matching, using the <code>string match</code> function.</p>
<pre><code class="language-fish">        set success 0
        set extension (string match -r &quot;.*(\.[^\.]*)\$&quot; $i)[2]
</code></pre>
<p>Now, we&rsquo;ve reached the main switch statement, which is largely a translation of the <code>zsh</code> version&rsquo;s unarchiving calls to <code>fish</code>.</p>
<pre><code class="language-fish">        switch $extension
            case '*.tar.gz' '*.tgz'
                tar xv; or tar zxvf &quot;$i&quot;
            case '*.tar.bz2' '*.tbz' '*.tbz2'
                tar xvjf &quot;$i&quot;
            case '*.tar.xz' '*.txz'
                tar --xz -xvf &quot;$i&quot;; or xzcat &quot;$i&quot; | tar xvf -
            case '*.tar.zma' '*.tlz'
                tar --lzma -xvf &quot;$i&quot;; or lzcat &quot;$i&quot; | tar xvf -
            case '*.tar'
                tar xvf &quot;$i&quot;
            case '*.gz'
                gunzip -k &quot;$i&quot;
            case '*.bz2'
                bunzip2 &quot;$i&quot;
            case '*.xz'
                unxz &quot;$i&quot;
            case '*.lzma'
                unlzma &quot;$i&quot;
            case '*.z'
                uncompress &quot;$i&quot;
            case '*.zip' '*.war' '*.jar' '*.sublime-package' '*.ipsw' '*.xpi' '*.apk' '*.aar' '*.whl'
                set extract_dir (string match -r &quot;(.*)\.[^\.]*\$&quot; $i)[2]
                unzip &quot;$i&quot; -d $extract_dir
            case '*.rar'
                unrar x -ad &quot;$i&quot;
            case '*.7z'
                7za x &quot;$i&quot;
            case '*'
                echo &quot;extract: '$i' cannot be extracted&quot; &gt;&amp;2
                set success 1
        end
</code></pre>
<p>Finally, we&rsquo;ll remove the original file if we&rsquo;ve successfully unarchived the file, and end the loop and the function.</p>
<pre><code class="language-fish">        if test $success -eq 0; and test $remove_file -eq 1
            rm $i
        end
    end
end
</code></pre>
<p>This was my first experience trying to port a larger function from zsh to fish, and it definitely took some playing around with the various test functions to get it right.
Also, the <code>string match</code> functions were largely cobbled together from StackOverflow.
I strongly suggest <a href="https://github.com/stefanvanburen/dotfiles/blob/9e62163c674f3fef58a12d752daa78b4c5eeecbe/config.fish#L21">aliasing this function to <code>x</code></a>, or some other short sequence, for easier usage.</p>
<p>And voilà, we have a working general purpose extraction function, in fish!</p>
<p>🐠</p>
]]></content>
    <source:markdown><![CDATA[
I recently switched from [zsh](https://en.wikipedia.org/wiki/Z_shell) to [fish](https://fishshell.com/) as my shell of choice.
I liked the idea of starting from scratch, with the sane defaults that fish provides, as my [zsh configuration files were getting a bit out of control](https://github.com/stefanvanburen/dotfiles/commit/10d9acc84179425772597d5a4c34c70a8bddd906#diff-53cae0c7df819f6e6a8104beae0f53a1).

For the most part, the transition was fairly painless and straightforward.
However, I still miss a few of the zsh niceties that I had been used to over the years --- one being [`extract` function provided in oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh/blob/master/plugins/extract/extract.plugin.zsh).
Even after moving away from oh-my-zsh as my plugin manager in zsh, I had used my new manager to grab [just that plugin](https://github.com/stefanvanburen/dotfiles/blob/74dd7a02b83ca1874d721e242e0f466ca1f65692/zshrc#L13-L14) for my usage.

After a cursory google, it seemed like a fish port of the plugin didn't exist, so I decided to try to port over the plugin myself.
You can find the whole function [in my config.fish in my dotfiles](https://github.com/stefanvanburen/dotfiles/blob/9e62163c674f3fef58a12d752daa78b4c5eeecbe/config.fish#L65-L125).

First, we'll define a function named `extract` and give it a description.
I've also noted in a comment where this function was ported from.

```fish
function extract -d "extract files from archives"
    # largely adapted from
    # https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract
```

The function first checks if we have arguments --- if we have none, there's nothing to do, so we'll echo a usage string to stderr and exit.

```fish
    # no arguments, write usage
    if test (count $argv) -eq 0
        echo "Usage: extract [-option] [file ...] " >&2
        echo " Options:" >&2
        echo " -r, --remove    Remove archive after unpacking." >&2
        exit 1
    end
```

Next, we check to see if the `-r` / `--remove` option has been supplied as the first argument.
If it is, we'll remove the archive files after they've been successfully unarchived.
We also remove this argument so that our main loop can iterate correctly over the filenames.

```fish
    set remove_file 0
    if test $argv[1] = "-r"; or test $argv[1] = "--remove"
        set remove_file 1
        set --erase argv[1]
    end
```

Now comes the main loop, which iterates over the filenames supplied.

```fish
    for i in $argv[1..-1]
```

First, we ensure that the filename arguments are valid files:

```fish
        if test ! -f $i
            echo "extract: '$i' is not a valid file" >&2
            continue
        end
```

We default our success value to `0` --- if the file's extension isn't something we can deal with, we'll set this to `1` so that we can avoid removing the file even if the remove option is set.
Then, we grab the extension via some fish regex matching, using the `string match` function.

```fish
        set success 0
        set extension (string match -r ".*(\.[^\.]*)\$" $i)[2]
```

Now, we've reached the main switch statement, which is largely a translation of the `zsh` version's unarchiving calls to `fish`.

```fish
        switch $extension
            case '*.tar.gz' '*.tgz'
                tar xv; or tar zxvf "$i"
            case '*.tar.bz2' '*.tbz' '*.tbz2'
                tar xvjf "$i"
            case '*.tar.xz' '*.txz'
                tar --xz -xvf "$i"; or xzcat "$i" | tar xvf -
            case '*.tar.zma' '*.tlz'
                tar --lzma -xvf "$i"; or lzcat "$i" | tar xvf -
            case '*.tar'
                tar xvf "$i"
            case '*.gz'
                gunzip -k "$i"
            case '*.bz2'
                bunzip2 "$i"
            case '*.xz'
                unxz "$i"
            case '*.lzma'
                unlzma "$i"
            case '*.z'
                uncompress "$i"
            case '*.zip' '*.war' '*.jar' '*.sublime-package' '*.ipsw' '*.xpi' '*.apk' '*.aar' '*.whl'
                set extract_dir (string match -r "(.*)\.[^\.]*\$" $i)[2]
                unzip "$i" -d $extract_dir
            case '*.rar'
                unrar x -ad "$i"
            case '*.7z'
                7za x "$i"
            case '*'
                echo "extract: '$i' cannot be extracted" >&2
                set success 1
        end
```

Finally, we'll remove the original file if we've successfully unarchived the file, and end the loop and the function.

```fish
        if test $success -eq 0; and test $remove_file -eq 1
            rm $i
        end
    end
end
```

This was my first experience trying to port a larger function from zsh to fish, and it definitely took some playing around with the various test functions to get it right.
Also, the `string match` functions were largely cobbled together from StackOverflow.
I strongly suggest [aliasing this function to `x`](https://github.com/stefanvanburen/dotfiles/blob/9e62163c674f3fef58a12d752daa78b4c5eeecbe/config.fish#L21), or some other short sequence, for easier usage.

And voilà, we have a working general purpose extraction function, in fish!

🐠
]]></source:markdown>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Hello, world!]]></title>
    <link href="https://stefan.vanburen.xyz/blog/hello-world/" rel="alternate" type="text/html"/>
    <id>https://stefan.vanburen.xyz/blog/hello-world/</id>
    <published>2019-11-13T14:47:53-05:00</published>
    <updated>2022-02-03T08:58:25-05:00</updated>
    <content type="html"><![CDATA[<p>Just an introductory post here, nothing to see.</p>
]]></content>
    <source:markdown><![CDATA[
Just an introductory post here, nothing to see.
]]></source:markdown>
  </entry>
  
</feed>
