llimllib's notes http://notes.billmill.org/ 2026-04-23T12:03:28.177431Z Obsidian Notes Bill Mill https://notes.billmill.org/blog/2025/07/An_AI_tool_I_find_useful.html An AI tool I find useful 2025-07-27T14:31:07.68Z 2026-04-16T02:29:32.853Z <p><strong>update</strong>: This tool has been superseded by <a href="/AI/tools/pr-review.html" class="internal-link">pr-review</a>, a similar but more powerful tool</p> <p>One of the tasks that I do most often is to review code. I've written a <a href="https://github.com/llimllib/personal_code/blob/032c597d4c1f805a0fb6030723e22fcf4349b2ef/homedir/.local/bin/review" class="external-link">review command</a> that asks an AI to review a code sample, and I've gotten a lot of value out of it.</p> <p>I ignore <em>most</em> of the suggestions that the tool outputs, but it has already saved me often enough from painful errors that I wanted to share it in the hope that others might find it useful.</p> <h2 id="how-to-install-it">How to install it</h2> <ul> <li>install <a href="https://github.com/simonw/llm" class="external-link">llm</a> and configure it to use the provider and model you'd like to use <ul> <li>on a mac, <code>brew install llm</code></li> </ul> </li> <li>optionally install <a href="https://github.com/sharkdp/bat" class="external-link">bat</a> <ul> <li>on a mac, <code>brew install bat</code></li> </ul> </li> <li>save the <a href="https://github.com/llimllib/personal_code/blob/c85d6b2106e1369339d045e8be0e43484149848e/homedir/.local/bin/review" class="external-link">review script</a> anywhere on your path and make it executable</li> </ul> <h2 id="how-it-works">How it works</h2> <p>The main job of the script is to generate context from a git diff and pass it to <a href="https://github.com/simonw/llm" class="external-link">llm</a> for code review.</p> <p>If you run <code>review</code> with no arguments, it will:</p> <ul> <li>run <code>git diff -U10</code> <ul> <li>the <code>-U</code> argument changes the amount of context given in a git diff</li> <li>any additional arguments you pass will be forwarded directly to <code>git diff</code></li> </ul> </li> <li>estimate the tokens in the output and check if that fits within the context window <ul> <li>shrink the context window if necessary and re-run <code>git diff</code></li> <li>if you want to manually expand the context beyond 10 lines <ul> <li>you can append to the system prompt with the <code>--context</code> flag</li> <li>you can expand the diff context to 100 by passing <code>-U100</code> or any number you prefer</li> </ul> </li> </ul> </li> <li>pipe the context to <code>llm</code> <ul> <li>You can configure <code>llm</code> to use whichever model or AI company you prefer</li> </ul> </li> <li>highlight the output with <code>bat</code> if available <ul> <li><a href="https://github.com/sharkdp/bat" class="external-link">bat</a> is great and I highly recommend using it if you don't already. <code>brew install bat</code> on a mac will install it</li> </ul> </li> </ul> <p>The result looks like this in my terminal:</p> <p><a href="/images/Pasted image 20250727105305.png"><img class="bodyimg" src="/images/Pasted image 20250727105305.png"></a></p> <h2 id="how-i-use-it">How I use it</h2> <p>My main use of the command is to review a PR I'm preparing before I file it. The <strong>biggest value</strong> I've gotten out of it is that it frequently catches embarrassing errors before I file a PR - misspellings, <code>DELETEME</code>s I forgot to remove, and occasionally logic errors.</p> <p>It also often suggests features that make sense to add before finishing the PR, or as next steps.</p> <p>It is very important to <strong>use it intelligently</strong>! The LLM is just an LLM, and it also may be missing context. The screenshot above has two examples of mistaken suggestions that I read and ignored; you have to apply your own understanding and taste to its output.</p> <p>Keep in mind that it is tasked via its system prompt with finding problems and making suggestions; no matter how good your code is it will try to find and suggest something.</p> <p>I also use it for reviewing other people's PRs, with <code>review origin/main origin/some-feature-branch</code>. In these cases, I really am just using it for clues as to some things that I may need to investigate with my actual human brain. Please <strong>do not just dump llm suggestions into a PR</strong>! That's both rude and likely to be unhelpful.</p> <h2 id="how-it-differs">How it differs</h2> <p>That last point brings me to why I prefer this tool to github's own copilot review tool.</p> <ul> <li>I can use my <code>review</code> tool in private, and evaluate its suggestions in private</li> <li>I can run it repeatedly as I change the code</li> <li>The separation of my terminal from the code review tool provides a space for me to apply critical thinking</li> </ul> <h2 id="areas-for-improvement">Areas for improvement</h2> <ul> <li>A lot of things are hard-coded into the script, because I'm its only user <ul> <li>If you find use in it, please let me know!</li> </ul> </li> <li>the system prompt seems to work fine, but the range of possible system prompts is so large that I'm sure it could be better</li> </ul> <h2 id="postscript">Postscript</h2> <p>Thanks to <a href="https://lobste.rs/c/mx0moq" class="external-link">a suggestion</a> on <a href="http://lobste.rs" class="external-link">lobste.rs</a> from <code>davidcrespo</code>, I <a href="https://github.com/llimllib/personal_code/commit/032c597d4c1f805a0fb6030723e22fcf4349b2ef" class="external-link">added the ability</a> to provide context via stdin. Thanks for the suggestion!</p> https://notes.billmill.org/blog/2024/02/How_to_love_homebrew.html How to love homebrew 2024-02-27T19:40:00Z 2026-03-16T20:30:38.774Z <p><a href="/computer_usage/homebrew/homebrew.html" class="internal-link">Homebrew</a> is a package manager which a lot of people use, and as software that a lot of people use, a lot of people like to complain about.</p> <p>I'm not here to tell you not to complain about it, but I <em>do</em> want to share why I love homebrew, and how you might grow to - if not love it - at least find it a part of a productive workflow.</p> <h2 id="my-one-iron-rule-of-homebrew">My One Iron Rule of Homebrew</h2> <p><strong>Don't rely on the particular version of something installed via homebrew</strong></p> <p>Homebrew, in my workflow, has one job: install command line software and keep it up to date. For quite a lot of software, the only thing I care about is &quot;is it up to date&quot; - and this is the software that I install with homebrew.</p> <p>For example, I use <code>ffmpeg</code> on the command line semi-regularly. <code>ffmpeg</code> has a bunch of dependencies and is generally kind of a pain in the ass to install and keep up to date on its own.</p> <p>But with homebrew, I just <code>brew install ffmpeg</code>, and if I need to update it, 💥 <code>brew upgrade ffmpeg</code> gets me the newer version. This works great!</p> <h2 id="homebrew-owns-the-things-it-installs">Homebrew owns the things it installs</h2> <p>The most common case I see where people get frustrated is when they do something like <code>brew install python</code>, and then start relying on the python version they've just installed for running their own scripts.</p> <p>In my mental model, when I say <code>brew install python</code>, I am telling homebrew <em>&quot;go ahead and install a version of python for yourself&quot;</em>, rather than <em>&quot;install a version of python for me&quot;</em>.</p> <p>Later on, if I were to update some package in homebrew that depends on some other package in homebrew, it <strong>would</strong> update my python version without telling me, and if I'm depending on that python version, I would be upset because it would either cease to exist or become a broken shell until it gets cleaned up.</p> <p><strong>When I install a package with homebrew, homebrew owns it and may upgrade it at any time</strong></p> <h2 id="i-use-asdf-for-versions-i-want-to-control-manually">I use asdf for versions I want to control manually</h2> <p><em>update march 2026</em>: Since I published this article I've been using <a href="/computer_usage/asdf/mise.html" class="internal-link">mise</a> and it's one of my favorite tools, I no longer use <code>asdf</code> at all</p> <p>If I care about the particular version of a program, such as a programming language environment or a database server, I install it myself, either manually or with a tool such as <a href="/computer_usage/asdf/asdf.html" class="internal-link">asdf</a>.</p> <p>When I <code>asdf install python 3.12.1</code>, I have a good mental model of what happens: python version 3.12.1 is downloaded and installed by <a href="https://github.com/pyenv/pyenv/" class="external-link">pyenv</a> to my asdf directory. At that point, I can be confident that version 3.12.1 is going to be available from now until such time as I take some action to uninstall it.</p> <p><code>asdf</code> has plugins for most software, and some newer software such as rust can handle this version juggling for me. Here's a brief list of how I install some of the programming environments I care about:</p> <ul> <li>go: <a href="https://github.com/asdf-community/asdf-golang" class="external-link">asdf-golang</a> <ul> <li>and possibly <a href="https://pkg.go.dev/golang.org/dl/gotip" class="external-link">gotip</a> if I need development versions</li> </ul> </li> <li>javascript: <a href="https://github.com/asdf-vm/asdf-nodejs" class="external-link">asdf-nodejs</a></li> <li>postgres: <a href="https://github.com/smashedtoatoms/asdf-postgres" class="external-link">asdf-postgres</a></li> <li>python: <a href="https://github.com/asdf-community/asdf-python" class="external-link">asdf-python</a></li> <li>ruby: <a href="https://github.com/asdf-vm/asdf-ruby" class="external-link">asdf-ruby</a></li> <li>rust: use <a href="https://rustup.rs/" class="external-link">rustup</a></li> <li>terraform: <a href="https://github.com/asdf-community/asdf-hashicorp" class="external-link">asdf-hashicorp</a></li> </ul> <p>(update: as per several commenters (thanks!) I'm now giving <a href="/computer_usage/asdf/mise.html" class="internal-link">mise</a> a try)</p> <h2 id="custom-taps-are-generally-reliable">Custom taps are generally reliable</h2> <p>Some software, such as <code>mongodb</code>, provides <a href="https://github.com/mongodb/homebrew-brew" class="external-link">custom taps for installation</a>.</p> <p>In my experience, these have been reliable, and I break my &quot;iron rule&quot; for mongo by installing <code>mongodb/brew/mongodb-community@&lt;version&gt;</code> when I need to run mongo.</p> <p>But that's not as catchy I suppose.</p> <h2 id="why-do-i-love-homebrew">Why do I love homebrew</h2> <p>If it seems like I've laid out a bunch of exceptions and rules to follow to avoid problems, why do I actually like it?</p> <ul> <li>For software that I just want to stay up to date, which is quite a lot of the software I use at the CLI, homebrew does a great job of keeping it up to date</li> <li>homebrew's coverage of modern software releases is extensive</li> <li>package versions update nearly immediately, so if I need an updated version of something I rarely have to wait</li> <li>many complex package installations are boiled down into <code>brew install &lt;package&gt;</code> <ul> <li>An example: <code>gdal</code> is a giant pain to install otherwise</li> </ul> </li> <li>it can install and keep GUI programs up to date</li> <li>creating your own recipes is very simple; see next section</li> </ul> <h2 id="creating-your-own-custom-tap-is-extremely-simple">Creating your own custom tap is extremely simple</h2> <p>If I have some software that I'd like to create a recipe for and allow other people to install via homebrew, it's reasonably easy to do.</p> <p>My github username is <code>llimllib</code>, and an example piece of software I wrote is <a href="https://github.com/llimllib/blisper" class="external-link">blisper</a>, which is a CLI and wrapper I wrote around <a href="https://github.com/ggerganov/whisper.cpp" class="external-link">whisper.cpp</a>. Here's how I made it installable via homebrew:</p> <ul> <li>Created a repository <a href="https://github.com/llimllib/homebrew-whisper/" class="external-link"><code>homebrew-whisper</code></a></li> <li>Added a <code>Formula</code> directory with a pair of <a href="https://docs.brew.sh/Formula-Cookbook" class="external-link">formulas</a> in it <ul> <li>each is <a href="https://github.com/llimllib/homebrew-whisper/blob/main/Formula/blisper.rb" class="external-link">short, declarative, and simple</a></li> </ul> </li> </ul> <p>After that, I can just tell people to run <code>brew install llimllib/whisper/blisper</code> and they should be up and running with my software.</p> <h2 id="simplicity">Simplicity</h2> <p>Why I love homebrew is wrapped up in how simple that process is; it isn't fit for everything, but it is simple enough that if you invest a bit of energy into understanding how it works, you can easily comprehend it well.</p> <p>It's an incomplete package manager that doesn't try to solve every problem - particularly it's uninterested in letting you run a particular version of any given software - but it's very good at solving the problem of installing the most recent version of CLI tools.</p> <p>I have found the pairing of <code>homebrew</code> and <code>asdf</code> to satisfy my needs as somebody that hacks on a lot of different software in a lot of different ecosystems, and I hope sharing my process and a bit of my mental model might help you figure out something that works well for you.</p> <p>If you have comments to make, I'm on <a href="https://hachyderm.io/@llimllib/112005194762232755" class="external-link">mastodon</a> and happy to talk about how I use homebrew and asdf, and manage my computer generally.</p> https://notes.billmill.org/blog/2026/01/my_tools_in_2026.html my tools in 2026 2026-01-03T18:35:04.419Z 2026-01-03T18:35:04.419Z <p>Here's a brief survey of the tools I'm currently using</p> <ul> <li><a href="#computer">Computer</a></li> <li><a href="#software">Software</a> <ul> <li><a href="#editor">editor</a></li> <li><a href="#neovim-plugins">neovim plugins</a></li> <li><a href="#terminal">terminal</a></li> <li><a href="#terminal-software">terminal software</a></li> </ul> </li> <li><a href="#browser">Browser</a> <ul> <li><a href="#browser-plugins">browser plugins</a></li> </ul> </li> <li><a href="#note-taking">Note Taking</a></li> <li><a href="#mail">Mail</a></li> <li><a href="#chat">Chat</a></li> <li><a href="#music">Music</a></li> <li><a href="#webcam">Webcam</a></li> <li><a href="#photos">Photos</a></li> <li><a href="#video">Video</a></li> </ul> <h1 id="computer">Computer</h1> <p>I use a 14-inch MacBook Pro M1 Max that I bought in 2021. It is, on balance, the best computer I have ever owned. The keyboard is usable (not the best macbook keyboard ever, but... fine), the screen is lovely, it sleeps when it's supposed to sleep and the battery still lasts a long time.</p> <p>The right shift key is broken, but using the left one hasn't proved to be much of a challenge.</p> <p>I usually replace my computers every 5 years, but I don't see any reason why I'd need a new one next year.</p> <h1 id="software">Software</h1> <h2 id="editor">editor</h2> <p>The most important piece of software on my computer is <a href="/computer_usage/vim/neovim.html" class="internal-link">neovim</a>. I've been using vim-ish software since 2003 or so, and on balance I think the neovim team has done a great job shepherding the software into the modern age.</p> <p>I get a bit irritated that I need to edit my configuration more often than I used to with Vim, but coding tools are changing so fast right now that it doesn't seem possible to go back to the world where I edited my config file once every other year.</p> <h3 id="neovim-plugins">neovim plugins</h3> <p>My vim config is available <a href="https://github.com/llimllib/personal_code/tree/master/homedir/.config/nvim" class="external-link">here</a>. I won't list all the plugins; there aren't <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.config/nvim/lua/lazyconfig.lua" class="external-link">that many</a>, and most of them are trivial, rarely-used or both. The ones I need are:</p> <ul> <li><a href="https://github.com/nvim-telescope/telescope.nvim" class="external-link">telescope.nvim</a> <ul> <li>I have &quot;open by filename search&quot; bound to <code>,ff</code> and &quot;open by grep search&quot; bound to <code>,fg</code>, and those are probably the two most common tools I use in vim. Telescope looks great and works fast (I use <a href="https://github.com/nvim-telescope/telescope-fzf-native.nvim" class="external-link">telescope-fzf-native</a> for grep)</li> </ul> </li> <li><a href="https://github.com/olimorris/codecompanion.nvim" class="external-link">codecompanion</a> <ul> <li>I added this in January last year, and it's become the default way I interact with LLMs. It has generally very nice features for working with buffers in vim and handles communications with LLMs cleanly and simply. You don't need Cursor et al to work with LLMs in your editor! <ul> <li>I do use <a href="https://claude.com/product/claude-code" class="external-link">claude code</a> for agentic missions, as I have not had much success with agentic mode in codecompanion - I use it more for smaller tasks while I'm coding, and it's low-friction enough to make that pretty painless</li> </ul> </li> </ul> </li> <li><a href="https://github.com/sainnhe/sonokai" class="external-link">sonokai</a> colorscheme <ul> <li>I use a <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.config/nvim/lua/colorscheme.lua" class="external-link">customized version</a>, and it makes me happy.</li> </ul> </li> </ul> <p>Now that vim has native LSP support, I could honestly probably get away with just those plugins. Here's a screenshot of what nvim looks like in a terminal window for me, with a telescope grep open:</p> <p><a href="/images/Pasted image 20260103135829.png"><img class="bodyimg" src="/images/Pasted image 20260103135829.png"></a></p> <h2 id="terminal">terminal</h2> <p>I use <a href="/computer_usage/terminals/kitty.html" class="internal-link">kitty</a> and my <a href="https://github.com/llimllib/personal_code/tree/master/homedir/.config/kitty" class="external-link">config is here</a>.</p> <p>I'd probably switch to <a href="/computer_usage/terminals/ghostty.html" class="internal-link">ghostty</a> if it <a href="https://github.com/ghostty-org/ghostty/discussions/9546" class="external-link">supported opening hyperlinks in a terminal application</a>, but it doesn't.</p> <p>I use this feature constantly so I want to explain a bit about why I find it so valuable. A common workflow looks like this:</p> <ul> <li>I <code>cd</code> to a project directory</li> <li>I either remember a file name or a string I can search for to find where I want to work <ul> <li>In the former case, I do <code>fd &lt;filename&gt;</code></li> <li>In the latter, I do <code>rg &lt;string&gt;</code></li> </ul> </li> <li>Then I click on the filename or line number to open that file or jump to that line number in a file</li> </ul> <p>Here's a video demonstrating how it works:</p> <p><video controls><source src="/images/Screen Recording 2026-01-03 at 2.07.35 PM.mov" type="video/quicktime" /><a href="/images/Screen Recording 2026-01-03 at 2.07.35 PM.mov">download</a></video></p> <p>The kitty actions are configured in <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.config/kitty/open-actions.conf" class="external-link">this file</a> - the idea is that you connect a mime type or file extension to an action; in this case it's <code>$EDITOR &lt;filename&gt;</code> for a filename without a line number, and <code>$EDITOR +${FRAGMENT} &lt;filename&gt;</code> if it has a line number.</p> <h3 id="terminal-software">terminal software</h3> <ul> <li>I love <a href="/computer_usage/asdf/mise.html" class="internal-link">mise</a> for managing versions of programming environments. I use it for node, terraform, go, python, etc etc <ul> <li>I have <code>mise.toml</code> files in most of my projects which set important environment variables, so it has replaced <a href="/computer_usage/cli_tips_and_tools/direnv.html" class="internal-link">direnv</a> for me as well</li> </ul> </li> <li><a href="/computer_usage/cli_tips_and_tools/fd.html" class="internal-link">fd</a> for finding files, a better <a href="/computer_usage/cli_tips_and_tools/find.html" class="internal-link">find</a></li> <li><a href="/computer_usage/cli_tips_and_tools/ripgrep.html" class="internal-link">ripgrep</a> for grepping</li> <li><a href="/programming/bash/atuin.html" class="internal-link">atuin</a> for recording my command line history</li> <li><a href="/programming/make/GNU_Make.html" class="internal-link">GNU Make</a> and occasionally <a href="/programming/task_runner/Just.html" class="internal-link">Just</a> for running tasks <ul> <li><code>make</code> is broken and annoying in old, predictable ways; but I know how it works and it's available everywhere <ul> <li><a href="https://github.com/llimllib/node-esbuild-executable/blob/main/Makefile" class="external-link">here's an example makefile</a> I made for a modern js project</li> </ul> </li> <li><code>just</code> is modern in important ways but also doesn't support output file targets, which is a feature I commonly use in <code>make</code>; see the above file for an example</li> </ul> </li> <li><a href="/computer_usage/git/gh.html" class="internal-link">gh</a> for interacting with github. I particularly use <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.zshrc#L303" class="external-link">my <code>pr</code> alias</a> for <code>gh pr create</code> quite a lot to open pull requests</li> <li><a href="/programming/json/jq.html" class="internal-link">jq</a> for manipulating json</li> <li><a href="/computer_usage/llm/llm.html" class="internal-link">llm</a> for interacting with LLMs from the command line; see <a href="/blog/2025/07/An_AI_tool_I_find_useful.html" class="internal-link">An AI tool I find useful</a> for an example</li> </ul> <h2 id="browser">Browser</h2> <p>I switched from <a href="/computer_usage/browsers/Firefox.html" class="internal-link">Firefox</a> to <a href="/computer_usage/browsers/Orion.html" class="internal-link">Orion</a> recently, mostly because I get the urge to switch browsers every six months or so when each of their annoyances accumulate.</p> <p>Orion definitely has bugs, and is slow in places, and Safari's inspector isn't nearly as nice as Firefox or Chrome. I wouldn't recommend other people switch, even though I'm currently enjoying it.</p> <h3 id="browser-plugins">browser plugins</h3> <ul> <li><a href="/computer_usage/password_managers/Bitwarden.html" class="internal-link">Bitwarden</a> - I dunno, it's fine, it mostly doesn't annoy me</li> <li><a href="/computer_usage/ad_blockers/uBlock_origin.html" class="internal-link">uBlock origin</a> - I'm very happy with it as an ad blocker</li> </ul> <p>That's it, I don't use any more. Browser plugins have a terrible security story and should be avoided as much as possible.</p> <h2 id="note-taking">Note Taking</h2> <p>I use <a href="/computer_usage/obsidian/Obsidian.html" class="internal-link">Obsidian</a>, which I publish to the web with <a href="https://github.com/llimllib/obsidian_notes" class="external-link">the code here</a> (see <a href="/computer_usage/obsidian/generating_HTML.html" class="internal-link">generating HTML</a>))</p> <p>It's not clear to me why I like using obsidian rather than just editing markdown files in vim, but I'm very happy with it.</p> <h2 id="mail">Mail</h2> <p>I use Apple's Mail.app. It's... fine enough I guess. I also occasionally use the fastmail web app, and it's alright too.</p> <p>I am very happy with Fastmail as an email host, and glad I switched from gmail a decade or so ago.</p> <h2 id="chat">Chat</h2> <p>I use Slack for work chat and several friend group chats. I hate it even though it's the best chat app I've ever used.</p> <p>I desperately want a chat app that doesn't suck and isn't beholden to Salesforce, but I hate Discord and IRC. I've made some minor attempts at replacing it with no success. It's the piece of software I use day to day that I would most love to replace.</p> <p>I also use Messages.app for SMS and texts</p> <h2 id="music">Music</h2> <p>I switched in October from Spotify to Apple Music. I dislike Apple Music, but I also disliked Spotify ever since I switched from Rdio when it died.</p> <p>I'm still on the lookout for a good music listening app. Maybe I'll try Qobuz or something? I don't know.</p> <p>I've also used <a href="/computer_usage/yt-dlp/yt-dlp.html" class="internal-link">yt-dlp</a> to download a whole bunch of concerts and DJ sets from youtube (see <a href="/music/youtube_concerts.html" class="internal-link">youtube concerts</a> for a list of some of them) and I often listen to those.</p> <h2 id="webcam">Webcam</h2> <p>I have an old iPhone mounted to my desk, and use <a href="/computer_usage/webcam/Reincubate_Camo.html" class="internal-link">Reincubate Camo</a> to connect it to video apps.</p> <p>I occasionally use <a href="/computer_usage/obs/OBS.html" class="internal-link">OBS</a> to record a video or add a goofy overlay to video calls, but not that often.</p> <h2 id="photos">Photos</h2> <p>I use Adobe Lightroom to import photos from my Fuji X-T30 and Apple Photos to manage photos.</p> <p>With the demise of flickr, I really have no place to post my photos and I've considered adding something to my website but haven't gotten it done.</p> <h2 id="video">Video</h2> <p>I use <a href="/computer_usage/media_players/VLC.html" class="internal-link">vlc</a> and <a href="/computer_usage/media_players/IINA.html" class="internal-link">IINA</a> for playing videos, and <a href="/computer_usage/ffmpeg/ffmpeg.html" class="internal-link">ffmpeg</a> for chopping them up from the command line</p> https://notes.billmill.org/blog/2025/12/2025_in_review.html 2025 in review 2025-12-31T14:54:50.522Z 2025-12-31T14:54:50.522Z <ul> <li><a href="#coding">coding</a></li> <li><a href="#tools">tools</a></li> <li><a href="#games">games</a></li> <li><a href="#travel">travel</a></li> <li><a href="#visualization">visualization</a></li> </ul> <h2 id="coding">coding</h2> <p>I didn't do that much work in the open this year, unfortunately. I made a few small tools and polished my workflow scripts quite a bit though:</p> <h3 id="review">review</h3> <p>I use my <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.local/bin/review" class="external-link">review script</a> to get feedback from an LLM on a pull request before I submit it, or to review other people's PRs. I like this a lot more than tools that annotate a PR in github, because it lets me read it locally and privately, and decide for myself which of its suggestions are valuable.</p> <p>As I <a href="https://notes.billmill.org/blog/2025/07/An_AI_tool_I_find_useful.html" class="external-link">wrote when I released the tool</a>, I find that <em>most</em> of its suggestions are not useful, but having a list of suggestions to evaluate has proved a very valuable resource for me and has prevented me from making quite a few obvious errors.</p> <h3 id="worktree">worktree</h3> <p>My <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.local/bin/worktree" class="external-link">worktree script</a> helps me <a href="https://notes.billmill.org/blog/2024/03/How_I_use_git_worktrees.html" class="external-link">use git worktrees</a> in a large node.js monorepo at work without so much pain. This year I:</p> <ul> <li>added submodule support</li> <li>added support for github fork notation (<code>user/repository</code>)</li> <li>added the ability to store its config in git global config</li> </ul> <p>I did play with <a href="https://notes.billmill.org/computer_usage/jujutsu/notes_on_giving_jj_a_try.html" class="external-link"><code>jj</code> a bit this year</a>, but I'm so used to git that it's not a pain point for me and it seems like a waste of time, though I remain interested in it.</p> <h3 id="time">time</h3> <p>My favorite <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.local/bin/%2Ctime" class="external-link">tiny script</a> of the year is my <code>time</code> script, which I wrote because I often have to compare times in different timezones with different time formats. For example, sometimes I'm reading timestamps from a log message in milliseconds since the epoch, and I need to know when that was both in my local time and in UTC.</p> <p>It has two functions:</p> <ul> <li>when called without arguments, prints out the current time in a variety of formats, useful for comparing time between timezones or formats:</li> </ul> <pre><code class="language-console"><div class="highlight"><pre><span></span><span class="gp">$ </span>,time <span class="go">ts seconds: 1767193930.0</span> <span class="go">ts ms: 1767193930000</span> <span class="go">Local: 2025-12-31 10:12:10 EST</span> <span class="go">UTC: 2025-12-31 15:12:10 UTC</span> </pre></div> </code></pre> <ul> <li>if passed an argument, will do its best to parse the time and display it in those same formats:</li> </ul> <pre><code class="language-console"><div class="highlight"><pre><span></span><span class="gp">$ </span>,time<span class="w"> </span><span class="m">1756082819</span> <span class="go">ts seconds: 1756082819.0</span> <span class="go">ts ms: 1756082819000</span> <span class="go">Local: 2025-08-24 20:46:59 EDT</span> <span class="go">UTC: 2025-08-25 00:46:59 UTC</span> <span class="gp">$ </span>,time<span class="w"> </span>Dec<span class="w"> </span><span class="m">31</span><span class="w"> </span><span class="m">2024</span> <span class="go">ts seconds: 1735621200.0</span> <span class="go">ts ms: 1735621200000</span> <span class="go">Local: 2024-12-31 00:00:00 EST</span> <span class="go">UTC: 2024-12-31 05:00:00 UTC</span> </pre></div> </code></pre> <h3 id="gp">gp</h3> <p>I have the rights to push to the main branch of a bunch of git repositories, and I found myself occasionally pushing directly to main by accident instead of creating a branch.</p> <p>To solve that problem, I replaced my <code>git push</code> alias with <a href="https://github.com/llimllib/personal_code/blob/master/homedir/.local/bin/gp" class="external-link">a script</a> that checks if I'm pushing to a main branch by accident, and displays a message to the console with the ability to do the push if it was indeed intentional.</p> <h2 id="tools">tools</h2> <p>I still spend most of my life in neovim in the terminal, and have no desire to change either of those.</p> <p>I'd like to switch to <a href="https://ghostty.org" class="external-link">ghostty</a>, but it doesn't support opening hyperlinks in terminal applications (<a href="https://github.com/ghostty-org/ghostty/discussions/9546" class="external-link">github discussion</a>). I find this workflow incredibly useful and don't want to live without it, so I'm still using <a href="https://sw.kovidgoyal.net/kitty/" class="external-link">kitty</a>.</p> <p>I've been using Obsidian to make this website, and remain very happy with it.</p> <p>I've updated the <a href="https://github.com/llimllib/obsidian_notes/blob/main/run.py" class="external-link">script that builds the website</a> in minor ways throughout the year. It's slow and I dislike the <a href="https://pypi.org/project/markdown-it-py/" class="external-link">markdown library it uses</a>, but it also gets the job done and I don't have to think about it.</p> <div class="admon-tip"><p class="tip-title">I added hacky callout support</p><p class="tip-content">But it was hacky and painful to do, so it goes</p></div> <p>I switched from Firefox to <a href="https://orionbrowser.com" class="external-link">Orion</a> browser, which is working well enough though it definitely has some bugs. I tend to switch browsers every six months as I accumulate dissatisfactions with all of them.</p> <h2 id="games">games</h2> <p>For a long time, I've vaguely wanted to play games that are Windows-only, but I also refuse to install Windows and I don't have a Linux gaming computer set up. A couple weeks ago, I finally bit the bullet and purchased <a href="https://www.codeweavers.com" class="external-link">CrossOver</a> and have started playing a few games that are Windows-only.</p> <p>Some games I or my kids enjoyed this year:</p> <ul> <li>We all enjoyed <a href="https://www.metanetsoftware.com/games/nplusplus" class="external-link">N++</a>, a platformer stripped down to the absolute basics. I remember playing <a href="https://www.metanetsoftware.com/games/n" class="external-link">n</a> as a flash game so many years ago, and its older sibling is still a blast.</li> <li>I enjoyed playing <a href="https://www.animalwell.net" class="external-link">Animal Well</a></li> <li>Because <a href="https://hollowknightsilksong.com" class="external-link">Silksong</a> came out this year, I went back and tried to finish <a href="https://www.hollowknight.com" class="external-link">Hollow Knight</a>. I managed to get to the last boss, but couldn't beat him, and didn't end up buying Siksong. Maybe next year</li> <li>My older son really enjoyed <a href="https://www.ballxpit.com" class="external-link">Ball x Pit</a></li> <li>My younger son got old enough to play <a href="https://www.nintendo.com/us/store/products/the-legend-of-zelda-tears-of-the-kingdom-switch/" class="external-link">Tears of the Kingdom</a> for himself, and enjoys coming up with silly weapon combinations more than actually playing the story, which is fun</li> <li>We had fun playing some <a href="https://subsetgames.com/ftl.html" class="external-link">FTL</a> runs together</li> <li>We all had fun with <a href="https://deadcells.com" class="external-link">Dead Cells</a></li> </ul> <h2 id="travel">travel</h2> <p>Working for a <a href="https://readme.com" class="external-link">distributed company</a> means traveling for meetups, and we had them in particularly fun locations this year. I was fortunate to travel to Colorado, Montana, and Mexico for work this year.</p> <p><a href="/images/0698C573-F6A1-4854-9697-CB3AC2CCB32B_1_105_c.jpeg"><img class="bodyimg" src="/images/0698C573-F6A1-4854-9697-CB3AC2CCB32B_1_105_c.jpeg"></a></p> <p><a href="/images/58CEAC05-38D6-4FC5-B259-72794BB0932D_1_105_c.jpeg"><img class="bodyimg" src="/images/58CEAC05-38D6-4FC5-B259-72794BB0932D_1_105_c.jpeg"></a></p> <p><a href="/images/E9BBB1DE-DF26-4BE3-9B67-DC56400E5492_1_105_c.jpeg"><img class="bodyimg" src="/images/E9BBB1DE-DF26-4BE3-9B67-DC56400E5492_1_105_c.jpeg"></a></p> <p>My family and I spent two weeks in Lecco, Italy, which was gorgeous. I'd never been to Italy before and it was great</p> <p><a href="/images/6ED76249-39D2-496A-BBD1-5551659B3E9B_1_105_c.jpeg"><img class="bodyimg" src="/images/6ED76249-39D2-496A-BBD1-5551659B3E9B_1_105_c.jpeg"></a></p> <h2 id="visualization">visualization</h2> <p>I continued toying around with different visualizations of NBA data this year. My son asked me the other day if it was work, and I told him that I make graphs in the same way that other guys build models or go fishing</p> <ul> <li>I kept up my <a href="https://github.com/llimllib/nba_data" class="external-link">nba_data</a> archive, and added a few data sources to it. I particularly enjoy Dean Oliver's <a href="https://espnanalytics.com/nba-daily-summary?date=20251230" class="external-link">analytics data</a>, and have found some good uses for it</li> <li>Lately I've been playing around with tables and trying to make them especially expressive; <a href="https://billmill.org/nba/table" class="external-link">this one</a> has a bar graph background that changes with the column you've sorted by, and has interactive filtering:</li> </ul> <p><a href="/images/Pasted image 20251231111825.png"><img class="bodyimg" src="/images/Pasted image 20251231111825.png"></a></p> <ul> <li>Due to a <a href="https://github.com/orgs/community/discussions/178318" class="external-link">particularly irritating github pages bug</a>, I moved my observable notebook off of github pages and over to <a href="https://billmill.org/nba" class="external-link">https://billmill.org/nba</a></li> <li>I post about this work on my <a href="https://bsky.app/profile/billmill.org" class="external-link">bluesky account</a>, and occasionally post a graph or progress on other work</li> </ul> https://notes.billmill.org/blog/2025/12/Advent_of_Code_2025.html Advent of Code 2025 2025-12-03T00:43:06.843Z 2025-12-03T00:43:06.843Z <p>This year I've decided to do Advent of Code in <a href="/programming/gleam/gleam.html" class="internal-link">Gleam</a>, a programming language I know nothing about and have never written a line of code in.</p> <p>Previously: <a href="/blog/2024/12/Advent_of_Code_2024.html" class="internal-link">Advent of Code 2024</a>, <a href="/programming/advent_of_code/2023_problem_log.html" class="internal-link">2023 problem log</a>, and incomplete answers back to 2015 <a href="https://github.com/llimllib/personal_code/tree/master/misc/advent/" class="external-link">in github</a></p> <ul> <li><a href="/blog/2025/12/Advent_of_Code_2025.html#day-1" class="internal-link">day 1</a></li> <li><a href="/blog/2025/12/Advent_of_Code_2025.html#day-2" class="internal-link">day 2</a></li> </ul> <h2 id="day-1">Day 1</h2> <p>Not the easiest day 1! The problem centered around a <a href="https://en.wikipedia.org/wiki/Ring_(mathematics)" class="external-link">ring</a> of the numbers 0-99.</p> <ul> <li>My answer for both parts is <a href="https://github.com/llimllib/personal_code/blob/master/misc/advent/2025/advent/src/day01/a.gleam" class="external-link">here</a></li> <li>I struggled with coming up with a simple way to express the crossings of zero for the second part, and I'm not super happy with how that came out</li> <li>You need a library to read files in gleam! <code>gleam add simplifile</code> to get the standard sync library it seems</li> <li><code>echo</code> is super handy for debugging pipelines - just stick it between <code>|&gt;</code> braces and it will show you what's going on</li> <li>I ought to figure out a better way to handle errors than just <code>unwrap</code>ing them and picking a random default value, it's definitely going to bite me eventually if I don't panic. I'm surprised there's not an <code>unwrap_or_panic</code> or something like that! Maybe I'll write it as <code>must</code> or something, but for today I just got by</li> </ul> <h2 id="day-2">Day 2</h2> <p>It seems that the creator of gleam doesn't like tuples, so today I tried not to use any. I started with a custom type to represent a range:</p> <pre><code class="language-gleam"><div class="highlight"><pre><span></span><span class="k">pub</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="n">Range</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Range</span><span class="p">(</span><span class="n">start</span><span class="p">:</span><span class="w"> </span><span class="n">Int</span><span class="p">,</span><span class="w"> </span><span class="n">end</span><span class="p">:</span><span class="w"> </span><span class="n">Int</span><span class="p">)</span> <span class="p">}</span> </pre></div> </code></pre> <p>For part 1, I used a generative approach where I iterated from the start of each range through the end, and checked if the number was of the form <code>&lt;x&gt;&lt;x&gt;</code>. The heart of it is this function:</p> <pre><code class="language-gleam"><div class="highlight"><pre><span></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="n">acc_dubs</span><span class="p">(</span><span class="n">front</span><span class="p">:</span><span class="w"> </span><span class="n">Int</span><span class="p">,</span><span class="w"> </span><span class="n">range</span><span class="p">:</span><span class="w"> </span><span class="n">Range</span><span class="p">,</span><span class="w"> </span><span class="n">acc</span><span class="p">:</span><span class="w"> </span><span class="n">List</span><span class="p">(</span><span class="n">Int</span><span class="p">))</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">let</span><span class="w"> </span><span class="n">dub</span><span class="w"> </span><span class="p">=</span> <span class="w"> </span><span class="k">string</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="k">int</span><span class="p">.</span><span class="n">to_string</span><span class="p">(</span><span class="n">front</span><span class="p">),</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">to_string</span><span class="p">(</span><span class="n">front</span><span class="p">))</span> <span class="w"> </span><span class="p">|&gt;</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">parse</span> <span class="w"> </span><span class="p">|&gt;</span><span class="w"> </span><span class="k">result</span><span class="p">.</span><span class="n">unwrap</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="n">dub</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="k">range</span><span class="p">.</span><span class="n">start</span><span class="p">,</span><span class="w"> </span><span class="n">dub</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="k">range</span><span class="p">.</span><span class="n">end</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">True</span><span class="p">,</span><span class="w"> </span><span class="n">True</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">acc_dubs</span><span class="p">(</span><span class="n">front</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">range</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">dub</span><span class="p">,</span><span class="w"> </span><span class="p">..</span><span class="n">acc</span><span class="p">])</span> <span class="w"> </span><span class="n">True</span><span class="p">,</span><span class="w"> </span><span class="n">False</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">acc</span> <span class="w"> </span><span class="n">False</span><span class="p">,</span><span class="w"> </span><span class="n">True</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">acc_dubs</span><span class="p">(</span><span class="n">front</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">range</span><span class="p">,</span><span class="w"> </span><span class="n">acc</span><span class="p">)</span> <span class="w"> </span><span class="n">False</span><span class="p">,</span><span class="w"> </span><span class="n">False</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="k">panic</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="s">&quot;impossible&quot;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </pre></div> </code></pre> <p>For part 2, I got in loops thinking about how to generate all possible combinations of numbers, so I instead took a filtering approach. This function checks if a number is symmetric in the way the problem states (assuming that there are no numbers with more than 10 digits):</p> <pre><code class="language-gleam"><div class="highlight"><pre><span></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="n">is_sym</span><span class="p">(</span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">let</span><span class="w"> </span><span class="n">ns</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="p">|&gt;</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">to_string</span> <span class="w"> </span><span class="k">let</span><span class="w"> </span><span class="n">l</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ns</span><span class="w"> </span><span class="p">|&gt;</span><span class="w"> </span><span class="k">string</span><span class="p">.</span><span class="n">length</span> <span class="w"> </span><span class="k">list</span><span class="p">.</span><span class="n">any</span><span class="p">(</span> <span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="n">atoi</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">repeat</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">slice</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">max</span><span class="p">(</span><span class="n">l</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">))),</span> <span class="w"> </span><span class="n">atoi</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">repeat</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">slice</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">),</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">max</span><span class="p">(</span><span class="n">l</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">))),</span> <span class="w"> </span><span class="n">atoi</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">repeat</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">slice</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">),</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">max</span><span class="p">(</span><span class="n">l</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">))),</span> <span class="w"> </span><span class="n">atoi</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">repeat</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">slice</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">),</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">max</span><span class="p">(</span><span class="n">l</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">))),</span> <span class="w"> </span><span class="n">atoi</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">repeat</span><span class="p">(</span><span class="k">string</span><span class="p">.</span><span class="n">slice</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">),</span><span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">max</span><span class="p">(</span><span class="n">l</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">))),</span> <span class="w"> </span><span class="p">],</span> <span class="w"> </span><span class="k">fn</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">==</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="p">&amp;&amp;</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="p">},</span> <span class="w"> </span><span class="p">)</span> <span class="p">}</span> </pre></div> </code></pre> <p>I find that pleasing!</p> <p>Then I iterated over every number in each range and checked it for symmetry, and summed the result:</p> <pre><code class="language-gleam"><div class="highlight"><pre><span></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="n">part_b</span><span class="p">(</span><span class="n">input</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">let</span><span class="w"> </span><span class="n">ranges</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">parse</span><span class="p">(</span><span class="n">input</span><span class="p">)</span> <span class="w"> </span><span class="k">list</span><span class="p">.</span><span class="n">fold</span><span class="p">(</span> <span class="w"> </span><span class="k">list</span><span class="p">.</span><span class="n">flat_map</span><span class="p">(</span><span class="n">ranges</span><span class="p">,</span><span class="w"> </span><span class="k">fn</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">acc_nubs</span><span class="p">(</span><span class="k">r</span><span class="p">.</span><span class="n">start</span><span class="p">,</span><span class="w"> </span><span class="k">r</span><span class="p">.</span><span class="n">end</span><span class="p">,</span><span class="w"> </span><span class="p">[])</span><span class="w"> </span><span class="p">}),</span> <span class="w"> </span><span class="mi">0</span><span class="p">,</span> <span class="w"> </span><span class="k">int</span><span class="p">.</span><span class="n">add</span><span class="p">,</span> <span class="w"> </span><span class="p">)</span> <span class="p">}</span> <span class="k">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="n">acc_nubs</span><span class="p">(</span><span class="n">start</span><span class="p">,</span><span class="w"> </span><span class="n">end</span><span class="p">,</span><span class="w"> </span><span class="n">acc</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="n">start</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">end</span><span class="p">,</span><span class="w"> </span><span class="n">is_sym</span><span class="p">(</span><span class="n">start</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">True</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">acc</span> <span class="w"> </span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">True</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">acc_nubs</span><span class="p">(</span><span class="n">start</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">end</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">start</span><span class="p">,</span><span class="w"> </span><span class="p">..</span><span class="n">acc</span><span class="p">])</span> <span class="w"> </span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">False</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">acc_nubs</span><span class="p">(</span><span class="n">start</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">end</span><span class="p">,</span><span class="w"> </span><span class="n">acc</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </pre></div> </code></pre> <p>I didn't do any better at handling errors today! I think I will make that an emphasis tomorrow.</p> <p>Custom types were really simple, and I haven't yet hit any big gleam roadblocks. It takes me a minute to think of how to phrase problems recursively, but I like that it kind of forces you to break your code into small, clear functions.</p> <p>I wish there were better inline testing functionality in gleam, I don't want to put my tests out into a test directory. I'm kind of annoyed generally by how picky it is about the project structure - but I also see how it fits into the zen of the language, which minimizes your options to keep complexity under control.</p> https://notes.billmill.org/blog/2025/11/Licensing_will_not_save_us.html Licensing will not save us 2025-11-16T13:33:25.613Z 2025-11-16T13:33:25.613Z <p>I enjoy <a href="https://blog.muni.town/open-source-power/" class="external-link">this piece</a> by Erlend Sogge Heggen which argues that we, the open source developers, ought not to freely give away our work because it advantages the capitalists and fascists that are leveraging the fruits of our labor to make untold millions for themselves without giving back to the community they're building off or the world at large.</p> <p>I've been using the computer long enough to remember how hard it was to get your hands on software that was interesting and useful. Without open source software, I'm certain that computing would not have progressed as far as it has, and that I and many others would not have the careers we enjoy because we wouldn't have found a way in.</p> <p>The class of business leaders who have built on open source software (and who often started as developers themselves) has taken a heavy toll on the world without returning the value they owe, but I also fear returning to a world where a privileged class of people has access to the source code for every important application, and interested people have to choose whether to break the law to satisfy their curiosity.</p> <p>More and more developers are playing around with licensing to try and defend themselves from the predatory practices of the tech elite, and I'm here for it - but that cannot be the whole solution.</p> <p>Only organization and community can protect the fruits of OSS (or a future OSS-like?) labor from explotation. It can't be one community, because there are many different aims, cultures, and viewpoints, but there can be many interlinked communities sharing tools, knowledge and practice.</p> <p>The sooner we start building the practices, habits and (less importantly) software to make it easy to build, maintain, and own communities of practice, the sooner we will make it possible to share the fruits of our labor in a less-destructive manner. Licensing alone won't save us, we need to build stable social organizations and learn how to empower them.</p> https://notes.billmill.org/blog/2025/09/Writing_good_technical_articles_is_hard__we_should_do_more_of_it_anyway.html Writing good technical articles is hard, we should do more of it anyway 2025-09-24T10:55:59.621Z 2025-09-24T10:55:59.621Z <p>An <a href="https://anniemueller.com/posts/how-i-a-non-developer-read-the-tutorial-you-a-developer-wrote-for-me-a-beginner" class="external-link">article</a> is making the rounds about what it feels like to read a technical article when you don't understand the terminology</p> <ul> <li>Why do people feel OK putting a partially-assed project on github, but have a much higher bar for when they release a technical article?</li> <li>It's <em>fractally</em> difficult to write technical tutorials and documentation <ul> <li>Everybody comes to it with a different skillset</li> <li>You're writing for <em>everybody in the world</em> speaking every language</li> <li>Every single thing you write about</li> <li>Computers are so new that there's very little common baseline for everyone</li> <li>we don't even know what the fields are yet!</li> <li>There's little-to-no feedback</li> <li>People feel very free to give harsh criticism <ul> <li>That leaves only the thickest-skinned authors able to write</li> </ul> </li> <li>The worst part for me is the complete lack of feedback <ul> <li>You chuck an article out into the ether, and either you hear <em>nothing</em> or the feedback you get ranges from mildly critical to harshly critical</li> <li>There's neither reason nor incentive to leave positive things about it. Who cares that you enjoyed the article</li> </ul> </li> </ul> </li> <li> <h2 id="lets-try-to-create-a-world-of-constructive-dialogue">Let's try to create a world of constructive dialogue</h2> </li> </ul> https://notes.billmill.org/blog/2025/07/A_pretty_decent_retry__and_not_a_library.html A pretty decent retry, and not a library 2025-07-19T15:14:41.434Z 2025-07-21T15:12:06.849Z <p>I'm pretty happy with the <a href="https://github.com/llimllib/nba_data/blob/dbbed16c1c05a118b98e92da099267f3ffb7a8cd/src/stats.py#L37-L72" class="external-link">retry function</a> I've ended up with for my <a href="https://github.com/llimllib/nba_data/" class="external-link">nba stats downloader</a>:</p> <pre><code class="language-python"><div class="highlight"><pre><span></span><span class="n">G</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&quot;G&quot;</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">retry</span><span class="p">(</span><span class="n">f</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">G</span><span class="p">],</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">G</span><span class="p">:</span> <span class="w"> </span><span class="sd">&quot;&quot;&quot;</span> <span class="sd"> Retry a function call with backoff and print out the exceptions raised</span> <span class="sd"> unless they are a timeout.</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">while</span> <span class="mi">1</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="k">return</span> <span class="n">f</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span> <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">11</span><span class="p">:</span> <span class="k">raise</span> <span class="n">RetryLimitExceeded</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="n">exc</span><span class="p">)</span> <span class="n">timeout</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">100</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;failed </span><span class="si">{</span><span class="n">f</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">(</span><span class="si">{</span><span class="n">kwargs</span><span class="si">}</span><span class="s2">), sleeping </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">:</span><span class="se">\n</span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span> <span class="c1"># print the traceback unless it&#39;s a timeout</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">exc</span><span class="p">,</span> <span class="p">(</span><span class="n">ReadTimeout</span><span class="p">,</span> <span class="n">ReadTimeoutError</span><span class="p">,</span> <span class="ne">TimeoutError</span><span class="p">)):</span> <span class="nb">print</span><span class="p">(</span><span class="n">traceback</span><span class="o">.</span><span class="n">format_exc</span><span class="p">())</span> <span class="n">sleep</span><span class="p">(</span><span class="n">timeout</span><span class="p">)</span> <span class="c1"># The LSP doesn&#39;t detect that this is unreachable: appease it</span> <span class="k">raise</span> <span class="ne">Exception</span><span class="p">(</span><span class="s2">&quot;this should never happen&quot;</span><span class="p">)</span> </pre></div> </code></pre> <h2 id="be-explicit">Be explicit</h2> <p>One thing I like about it is that it doesn't calculate a backoff value - it picks from an explicit list of backoffs.</p> <p>Before I wrote this one, I had written a bunch of retry functions and always tried to come up with some clever exponential algorithm and do a bit of math. One day I was working with backoffs for my day job in a database context and I decided to go look at <a href="https://github.com/sqlite/sqlite/blob/1f436ad563c4f16b94060a1982ae41abac015053/src/main.c#L1709-L1737" class="external-link">what sqlite uses</a>:</p> <pre><code class="language-c"><div class="highlight"><pre><span></span><span class="k">static</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">u8</span><span class="w"> </span><span class="n">delays</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">};</span> <span class="n">delay</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">delays</span><span class="p">[</span><span class="n">count</span><span class="p">];</span> <span class="c1">// ed: That&#39;s &quot;sqlite3 OS sleep&quot;, not &quot;sqlite 30s sleep&quot;</span> <span class="n">sqlite3OsSleep</span><span class="p">(</span><span class="n">db</span><span class="o">-&gt;</span><span class="n">pVfs</span><span class="p">,</span><span class="w"> </span><span class="n">delay</span><span class="o">*</span><span class="mi">1000</span><span class="p">);</span> </pre></div> </code></pre> <p>It's pleasantly <a href="https://grugbrain.dev" class="external-link">grug-brained</a>; why use clever code when simple code do trick?</p> <p>So I stole the idea and I use it whenever I need a retry function now.</p> <h2 id="dont-be-generic">Don't be generic</h2> <p>The retry function does not allow you to specify your timeouts or configure the list of exceptions that don't get tracebacks or configure the message that gets thrown.</p> <p>It doesn't support regular args because I only need kwargs in the context I'm using it in.</p> <p>If I needed to use it again, I'd probably have to modify one or both of those choices. That's OK!</p> <p>This leads to:</p> <h2 id="dont-create-a-library-for-small-stuff">Don't create a library for small stuff</h2> <p>One instinct for a free software developer, when they have a pleasant battle-tested function, is to make it into a library. The Javascript ecosystem is full of libraries this small or smaller - I could go look and I'm sure I'd find seventeen similar examples of retry libraries.</p> <p>It feels nice, like you're giving back to the community that you've drawn value from.</p> <p>But it's a mirage!</p> <p>The value of having a small function like this in your codebase, where you can see it, read it, and tailor it to suit your needs is greater than the value of having a highly configurable library that's difficult to read and debug.</p> <p>As the code gets more complex, the weight of that complexity can make it such that it starts to make sense to isolate the code into a library and share it.</p> <p>I'd like to advocate that people usually underweight how much value there is in having the code included in your application by tailoring it to your application exactly rather than abstracting it into a library.</p> <h2 id="toolboxes-not-libraries">Toolboxes, not libraries</h2> <p>For small code tasks like this, it's better to have a toolbox of examples you can pull from and customize.</p> <p>If I needed retry in a golang or rust program tomorrow, I could easily remember that I've written this code and translate it for use. If I'd instead used a retry library, I'd have to find one in that ecosystem and figure out which library was best, how to use it, and add an external dependency.</p> <p>This is much of the value that StackOverflow has provided to the world: a giant set of comments containing small, customizable code examples such as this one that solve small problems, which people can copy and modify as they wish.</p> <p>Given that StackOverflow seems to be dying, I wonder how we could do a better job supporting this &quot;toolbox&quot; type of code?</p> <p>Would it make sense for language ecosystems to host toolboxes? What if python had a <code>python.org/examples</code> library where people can paste code examples with a description and usage guide, and perhaps star them when they find them useful?</p> <p>Or perhaps we could try to have a sort of &quot;codex&quot; by problem type, where <code>retry</code> had a thousand examples in different languages, with different techniques, usages, and lineages?</p> <h2 id="postscript">postscript</h2> <ul> <li>Kartik Agaram <a href="https://merveilles.town/@akkartik/114880743779511619" class="external-link">points to</a> <a href="https://akkartik.name/post/four-repos" class="external-link">this excellent post</a> about four repositories he's created and uses, that he calls &quot;template repositories&quot;, similar to the &quot;toolbox&quot; idea</li> <li>Konrad Hinsen <a href="https://scholar.social/@khinsen/114891340725097727" class="external-link">links</a> this <a href="https://osf.io/preprints/metaarxiv/nt96q_v1" class="external-link">preprint article</a> about what it means to establish trust in scientific code. I like this:</li> </ul> <blockquote> <p>The evolution of software in such a universe is very different from what we see today. There is no official repository, no development timeline, no releases. There is only a network of many variants of some code, connected by forking relations. Centralized maintenance as we know it today does not exist. Instead, the community of scientists using the code improves it in small steps, with each team taking over improvements from other forks if they consider them advantageous. Improvement thus happens by small-step evolution rather than by large-scale design. While this may look strange toanyone used to today’s software development practices, it is very similar to how scientific models and theories have evolved in the pre-digital era</p> </blockquote> https://notes.billmill.org/blog/2024/09/Comparing_golang_sqlite_to_C_sqlite.html Comparing golang sqlite to C sqlite 2024-10-01T12:19:11.675Z 2025-06-19T21:20:17.704Z <p>Following a <a href="https://www.reddit.com/r/golang/comments/1ft4udf/performance_write_intensive_sqlite_mattngosqlite3/" class="external-link">question on reddit</a>, I got curious about how well golang sqlite bindings (and translations, and WASM embeddings) compare to using C directly.</p> <p>I'm competent with C, but no expert, so I used the help of an LLM to translate a <a href="/programming/golang/database_libraries/go-sqlite-bench.html" class="internal-link">golang sqlite benchmark</a> to C and tried to make it as equivalent to the golang as possible in terms of the work it was doing.</p> <p>The <a href="https://github.com/cvilsmeier/go-sqlite-bench/blob/b0e7d08b69d685fc20fcbf4ac4de5f57b73c3720/app/app.go#L93" class="external-link">test I copied</a> is pretty simple: insert a million users into a database, then query the users table and create user structs for each.</p> <p>The result is just one test, but it suggests that all sqlite bindings (or translations) have trouble keeping up with sqlite in C. All times here are in milliseconds, and I picked the best of two runs:</p> <table> <thead> <tr> <th>library</th> <th>insert (ms)</th> <th>ratio</th> <th>query (ms)</th> <th>ratio</th> </tr> </thead> <tbody> <tr> <td>C sqlite3</td> <td>737</td> <td>1.00</td> <td>84</td> <td>1.00</td> </tr> <tr> <td>mattn/sqlite3</td> <td>1380</td> <td>1.87</td> <td>1036</td> <td>12.33</td> </tr> <tr> <td><a href="http://crawshaw.io/sqlite" class="external-link">crawshaw.io/sqlite</a></td> <td>1097</td> <td>1.49</td> <td>542</td> <td>6.45</td> </tr> <tr> <td><a href="http://modernc.org/sqlite" class="external-link">modernc.org/sqlite</a></td> <td>4148</td> <td>5.63</td> <td>966</td> <td>11.50</td> </tr> <tr> <td>eatonphil/gosqlite</td> <td>1017</td> <td>1.38</td> <td>651</td> <td>7.75</td> </tr> <tr> <td>ncruces/go-sqlite3</td> <td>2178</td> <td>2.96</td> <td>611</td> <td>7.27</td> </tr> <tr> <td>zombiezen/go-sqlite</td> <td>1403</td> <td>1.90</td> <td>211</td> <td>2.51</td> </tr> <tr> <td>python 3.12.2</td> <td>1992</td> <td>2.70</td> <td>1426</td> <td>16.98</td> </tr> </tbody> </table> <p><a href="/images/Pasted image 20241001084907.png"><img class="bodyimg" src="/images/Pasted image 20241001084907.png"></a><br /> <a href="/images/Pasted image 20241001084951.png"><img class="bodyimg" src="/images/Pasted image 20241001084951.png"></a></p> <p>The tests were run with go 1.23.1 and clang 16.0.0 on an apple m1 mac with 32gb of ram.</p> <p>This is a deeply non-serious benchmark - I was browsing the web while I was running it, for starters. Still, the magnitude of the results indicates that sqlite in C is still quite a bit faster than any of the alternatives in go.</p> <p>Somebody on mastodon asked me how this compares to previous versions of go, so I did a brief test of mattn and crawshaw on go 1.19, and found that they were in the range of 10-15% slower, so real progress on making cgo faster has been made in a pretty short timeframe.</p> <p>The code is available <a href="https://gist.github.com/llimllib/4cb06c5fe7439aa7f3cb67a818fa230d#file-sqlite-c" class="external-link">here</a> in a gist.</p> <h2 id="update">update</h2> <p>For kicks, I added a python version to the table above; source is <a href="https://gist.github.com/llimllib/4cb06c5fe7439aa7f3cb67a818fa230d#file-bench-py" class="external-link">in the same gist</a></p> <p><em>2025 Jun 19</em>: noticed that the python ratios were not correct and fixed them; I did not re-run any tests</p> https://notes.billmill.org/blog/2025/06/Federation_is_extremely_expensive.html Federation is extremely expensive 2025-06-19T14:59:05.557Z 2025-06-19T16:04:16.19Z <p>There is a certain feeling among the developers of messaging software that if you want to create something worthwhile, you need to create a federated system.</p> <p>This is how you get <a href="https://en.wikipedia.org/wiki/ActivityPub" class="external-link">ActivityPub</a>, <a href="https://atproto.blue/en/latest/readme.html" class="external-link">atproto</a>, <a href="https://matrix.org/docs/chat_basics/matrix-for-im/" class="external-link">matrix</a>, <a href="https://xmpp.org" class="external-link">xmpp</a>, <a href="https://nostr.com" class="external-link">nostr</a>, and on and on and on. A series of protocols that aim to recreate the successes of email and IRC.</p> <h3 id="the-successes-are-overblown">The successes are overblown</h3> <p>Email and IRC have huge problems, many of which are caused by their own decentralization.</p> <p>Both protocols suffer from the extreme difficulty of upgrades - it's nearly impossible to organize a whole herd of people running servers to upgrade at the same time. The effect is that once a decentralized protocol has stabilized, it is extremely resistant to upgrades.</p> <p>Both protocols suffer from the challenge of preventing spam. It has proven so difficult for email that is has de facto centralized around Google Mail and a few other major providers.</p> <p>Email's design is extremely substandard by modern standards, but despite the quasi-centralization of providers, we have no hope of changing it because there are still too many providers and consumers to make modernization possible. This leaves us with the worst of both worlds, a sub-standard protocol with a mostly-centralized structure.</p> <h3 id="moderation-is-impossible">Moderation is ~impossible</h3> <p>A key lesson of the modern internet is that a well moderated service is essential to providing a decent experience for users who are not trolls.</p> <p>If your service is truly decentralized, consistent moderation is impossible by design.</p> <p>Taking on federation means taking on the Herculean task of coordinating decentralized moderation. I don't think it's <em>impossible</em> to do an OK job of it, but it's a huge task and an extreme social challenge.</p> <h3 id="ownership-is-important">Ownership is important</h3> <p>I think a lot of the desire for federated systems comes down to a desire for <strong>ownership</strong>; the ability of a person or a group of people to own some piece of the system. It feels like in a decentralized system, even if you don't, you <em>could</em> own a piece of it. You could stand up an email server, or a mastodon server, and do things your own way if you had to.</p> <p>This is an excellent impulse, and we should nurture it!</p> <p>But federation is not the only way to achieve ownership. We can make open software that allows people to own their own computation in meaningful ways a lot faster by <strong>skipping federation</strong> and focusing instead on building software that delivers a stronger user interface and user experience while providing data ownership.</p> <p>I want to see is a world in which we can deliver more open software to develop communities more rapidly, and I think we might get there faster if we accept that the cost of federation is so high that we ought to consider avoiding it.</p> <h3 id="postscript">postscript</h3> <p>This post follows a conversation I had on a private Slack. Part of the reason why I think that conversation happened on Slack instead of on an open chat system is that we have spent so much effort building federated systems that we've failed to deliver working open software to replace it.</p> https://notes.billmill.org/blog/2024/12/Triple_Storyline_for_Ethical_Design.html Triple Storyline for Ethical Design 2024-12-07T02:01:11.598Z 2024-12-07T02:01:11.598Z <blockquote> <p>A good user experience is only as good as the action it enables. Designing a system that makes it easy to <a href="https://www.nytimes.com/2018/03/27/nyregion/facebook-housing-ads-discrimination-lawsuit.html" class="external-link">do bad things</a> is bad. A system that automates discrimination is, by Spinoza’s light, evil.</p> </blockquote> <ul> <li>[Erika Hall](A good user experience is only as good as the action it enables. Designing a system that makes it easy to <a href="https://www.nytimes.com/2018/03/27/nyregion/facebook-housing-ads-discrimination-lawsuit.html" class="external-link">do bad things</a> is bad. A system that automates discrimination is, by Spinoza’s light, evil.) via <a href="https://mastodon.social/@RuthMalan/113608939667606692" class="external-link">mastodon</a></li> </ul> https://notes.billmill.org/blog/2024/12/Advent_of_Code_2024.html Advent of Code 2024 2024-12-06T21:41:44.068Z 2024-12-06T21:41:44.068Z <p>Last year I kept my advent of code log in a note on this site: <a href="/programming/advent_of_code/2023_problem_log.html" class="internal-link">2023 problem log</a></p> <p>This year I wanted to make it easier for me to generate and share visualizations, and break the code into more logical explanations, so I'm doing it at <a href="https://llimllib.github.io/advent2024/" class="external-link">https://llimllib.github.io/advent2024/</a> in an <a href="https://observablehq.com/framework/" class="external-link">observable framework</a> notebook.</p> <p>The source code is <a href="https://github.com/llimllib/advent2024" class="external-link">all on github</a>, but hopefully the notebook provides an easy reading experience.</p> <p>I've tried to do visualizations for anything that can be remotely visualized, and plan to play around with it some more and learn more about framework and plot.</p> https://notes.billmill.org/blog/2024/08/Colophon.html Colophon 2024-08-12T20:54:18.364Z 2024-08-12T20:54:18.364Z <p>To add to this blog, I type out a note in Obsidian; I use Obsidian with very few plugins.</p> <p><video controls><source src="/images/typenote.mp4" type="video/mp4" /><a href="/images/typenote.mp4">download</a></video></p> <p>When it's reasonably done, I back it up with the <a href="https://github.com/Vinzent03/obsidian-git" class="external-link">obsidian git</a> plugin by clicking the &quot;backup&quot; button</p> <p><a href="/images/Pasted image 20240812170051.png"><img class="bodyimg" src="/images/Pasted image 20240812170051.png"></a></p> <p>That backs it up to a private github repository.</p> <p>That repository has a <a href="https://gist.github.com/llimllib/19bbe748d13da49233d501da04e63fb8" class="external-link">github action</a> that runs <a href="https://github.com/llimllib/obsidian_notes/blob/d745eae34ac9f261baa7dbba093240b73a71945c/run.py" class="external-link">run.py</a> from my <a href="https://github.com/llimllib/obsidian_notes" class="external-link">obsidian_notes repository</a>.</p> <p>That script converts the big pile of Obsidian-flavored markdown into a big pile of HTML.</p> <p>The github action uploads that big pile of HTML to a <a href="https://www.digitalocean.com/products/spaces" class="external-link">Digital Ocean space</a> using <a href="/computer_usage/AWS_cli/s3cmd.html" class="internal-link">s3cmd</a>. (I <a href="https://hachyderm.io/@llimllib/112759682584953702" class="external-link">don't recommend</a> Digital Ocean spaces, but that's what I use for now)</p> <p>To serve <code>notes.billmill.org</code>, I have <a href="/programming/web_serving/caddy.html" class="internal-link">caddy</a> installed on my web server, proxying requests to <code>cdn.billmill.org</code>, which is a <code>CNAME</code> to the digital ocean space.</p> <p>That's it! Feel free to ask me questions on mastodon.</p> https://notes.billmill.org/blog/2024/08/How_to_debug_git_with_visual_studio_code_on_a_mac.html How to debug git with visual studio code on a mac 2024-08-09T13:26:26.812Z 2024-08-09T13:26:26.812Z <p>I needed to figure out how to debug the <code>git</code> binary to figure out exactly what it was doing, and I thought I'd share the setup because it took me a little while to get going.</p> <h1 id="prerequisites">Prerequisites</h1> <ul> <li>install <a href="https://code.visualstudio.com/" class="external-link">visual studio code</a></li> <li>install the <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools" class="external-link">microsoft C/C++ extension</a></li> <li>you will need to have <code>lldb</code>, the <a href="/programming/compilers/llvm.html" class="internal-link">llvm</a> debugger available on your system. <ul> <li>To see if you have <code>lldb</code> available, run <code>which lldb</code>; as long as it prints a path and not <code>lldb not found</code>, you're good to go</li> <li>The most common way to get it installed is to install <code>Xcode</code> from the app store or the <code>Xcode Command Line Tools</code> which can be installed with <code>xcode-select --install</code></li> <li>You may also install it with homebrew by doing <code>brew install llvm</code>. Be aware though that brew will not link it by default, so you'll have to figure out how to tell vs code to use it</li> </ul> </li> </ul> <h1 id="building-git">Building git</h1> <ul> <li>Clone the git repository: <code>git clone https://github.com/git/git</code></li> <li>Change into the cloned directory: <code>cd git</code></li> <li>Set some build variables by creating a file called <code>config.mak</code>. It should have exactly these contents:<pre><code class="language-makefile"><div class="highlight"><pre><span></span><span class="nv">DEVELOPER</span><span class="o">=</span><span class="m">1</span> <span class="nv">CFLAGS</span><span class="o">+=</span><span class="w"> </span>-O0 </pre></div> </code></pre> <ul> <li>A command you might use to make this file: <code>printf 'DEVELOPER=1\nCFLAGS+= -O0' &gt; config.mak</code></li> <li>see <a href="https://github.com/git/git/blob/25673b1c476756ec0587fb0596ab3c22b96dc52a/Makefile#L556" class="external-link">the Makefile</a> for more info on what <code>DEVELOPER</code> does</li> <li>the <code>CFLAGS</code> variable provides a flag to the C compiler to build a binary with no compiler optimizations; traditionally <code>O3</code> means &quot;the most optimizations&quot; and <code>O0</code> means &quot;no optimizations&quot; <ul> <li><a href="https://clang.llvm.org/docs/CommandGuide/clang.html#code-generation-options" class="external-link">clang documentation</a> on optimization options</li> </ul> </li> </ul> </li> <li><code>make -j4</code> <ul> <li>The <code>-j</code> argument to make tells it how much parallelism it can use. It speeds up the build by increasing parallelism; you can use more if you have more CPUs, or less if you have fewer. The build doesn't take very long though so it's not a big deal.</li> </ul> </li> <li>When this completes successfully, you should have a bunch of new binaries in your git directory, such as <code>git</code>, <code>git-add</code>, <code>git-am</code>, etc</li> </ul> <h1 id="debugging-git">Debugging git</h1> <ul> <li> <p>make a <code>.vscode</code> directory: <code>mkdir .vscode</code></p> </li> <li> <p>open the git directory with visual studio code: <code>code .</code></p> </li> <li> <p>create a file in the <code>.vscode</code> directory called <code>launch.json</code> that tells vs code how to start <code>git</code> for debugging. Its contents should be:</p> <pre><code class="language-json"><div class="highlight"><pre><span></span><span class="p">{</span> <span class="w"> </span><span class="nt">&quot;configurations&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;debug git&quot;</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;cppdbg&quot;</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;request&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;launch&quot;</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;program&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;${workspaceFolder}/git&quot;</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;args&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;status&quot;</span><span class="p">],</span> <span class="w"> </span><span class="nt">&quot;cwd&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;${workspaceFolder}&quot;</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;externalConsole&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;MIMode&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;lldb&quot;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">]</span> <span class="p">}</span> </pre></div> </code></pre> <ul> <li>Note that we're passing <code>status</code> as an argument; the first command we're going to debug is <code>git status</code>. To debug other commands, you'll want to change the <code>args</code> option</li> <li>See the <a href="https://code.visualstudio.com/docs/cpp/launch-json-reference" class="external-link">documentation</a> for information on these configuration options and what additional flags are available</li> </ul> </li> <li> <p>Open the <code>builtins/commit.c</code> file in the editor</p> </li> <li> <p>Set a breakpoint on the first line inside the <code>cmd_status</code> function; in my current version of git, that's located at line 1504</p> <ul> <li>To set a breakpoint, click on the left gutter, just to the left of the line numbers; you should see a red dot appear. Once a breakpoint is set, the debugger will stop when it reaches that location, allowing you to control execution of the program <ul> <li><a href="https://code.visualstudio.com/docs/editor/debugging#_breakpoints" class="external-link">here is VS Code's documentation</a> on how to set breakpoints</li> </ul> </li> <li>Annoyingly, setting a breakpoint at the function name doesn't seem to work for me, so I have to set a breakpoint <em>inside</em> the function or else the program doesn't break <ul> <li>If you really want to do this, in the command palette there's an action called <code>Add Function Breakpoint</code>, which created a function breakpoint that I could type <code>cmd_status</code> into, and that did break on the function invocation</li> </ul> </li> <li>The <code>builtins</code> directory contains most of the high-level git UI code</li> <li>git subcommands will have a function name <code>cmd_&lt;command name&gt;</code>; check out <code>cmd_log</code> or <code>cmd_commit</code> for other examples</li> </ul> </li> <li> <p>Switch to the <code>Run and Debug</code> tab of VS code</p> </li> <li> <p>click on <code>start debugging</code>, the green arrow located at the top left of the window, or press F5 to do the same thing.</p> <ul> <li>alternatively, open your command palette with <code>⇧⌘P</code>, type <code>Debug</code>, and select <code>Debug: Select and Start Debugging</code>, then select <code>debug git</code></li> </ul> </li> </ul> <p>If all goes well, you should now be controlling the execution of git inside VS code! Go ahead and dig in to try and understand what's going on.</p> <p>I might write more about what it is that's going on in there, but I'm not sure - let me know if you would like to understand some part of it better.</p> https://notes.billmill.org/blog/2024/07/A_command_to_print_terminal_color_codes.html A command to print terminal color codes 2024-07-19T13:05:13.988Z 2024-07-19T13:05:13.988Z <p>I write shell scripts that use terminal colors often enough that it's handy to know a bit about them, but not often enough that I remember how they work without looking it up.</p> <p>So, this morning, I wrote <code>colors</code>, a shell script that prints out tables of the basic ANSI colors and text effects, and demonstrates what they look like on your terminal.</p> <p><a href="https://github.com/llimllib/personal_code/blob/master/homedir/.local/bin/colors" class="external-link">Here's the script</a>, and here's what it looks like on a mostly-stock apple terminal.app with a dark color scheme:</p> <p><a href="/images/Pasted image 20240719095744.png"><img class="bodyimg" src="/images/Pasted image 20240719095744.png"></a></p> <p>You can pass <code>-v</code> to the script to get a table of the 8-bit colors, in case you want to get real fancy with your script:</p> <p><a href="/images/Pasted image 20240719094123.png"><img class="bodyimg" src="/images/Pasted image 20240719094123.png"></a></p>