<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[naildrivin5.com - David Bryant Copeland's Website]]></title>
  <link href="https://naildrivin5.com/atom.xml" rel="self"/>
  <link href="https://naildrivin5.com/"/>
  <updated>2026-04-13T18:09:56+00:00</updated>
  <id>https://naildrivin5.com/</id>
  <author>
    <name><![CDATA[David Bryant Copeland]]></name>
    <email><![CDATA[davec@naildrivin5.com]]></email>
  </author>

  
  <entry>
    <title type="html"><![CDATA[Talking Death of a Software Craftsman on the Dead Code Podcast]]></title>
    <link href="https://naildrivin5.com/blog/2026/03/24/dead-code-podcast.html"/>
    <updated>2026-03-24T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2026/03/24/dead-code-podcast.html</id>
    <content type="html"><![CDATA[<p>I joined Jared again on the <a href="https://shows.acast.com/dead-code/episodes/065-reject-modernity-with-david-copeland">Dead Code Podcast</a> to go a bit deeper on my
post, <a href="/blog/2026/02/23/the-death-of-the-software-craftsman.html">The Death of a Software Craftsman</a>.   The outlook is kinda bleak,
but plenty of digs at agile software practices :)</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Trying Linux Desktop Yet Again with More Success]]></title>
    <link href="https://naildrivin5.com/blog/2026/03/11/trying-linux-desktop-yet-again-with-more-success.html"/>
    <updated>2026-03-11T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2026/03/11/trying-linux-desktop-yet-again-with-more-success.html</id>
    <content type="html"><![CDATA[<p>Almost a year ago, <a href="/2025/03/21/one-week-with-desktop-linux-after-a-20-year-absence.html">I returned to the Linux Desktop</a> after almost 20 years.  I abandoned it a month or so later out of frustration with a surprising lack of configurability and general exhaustion of addressing the myriad papercuts that come with trying to change computing platforms.</p>

<p>In the last few weeks, I’ve revisited it and had a lot more success.</p>

<!-- more -->

<h2 id="the-core-problems">The Core Problems</h2>

<p>The main problems that kept me from adopting Linux day to day were:</p>
<ul>
  <li>Inability to set keyboard shortcuts for apps to provide a Mac-like experience (namely, using <kbd>Meta</kbd> or <kbd>Alt</kbd> instead of <kbd>Ctrl</kbd> so I could have the same common shortcuts everywhere).</li>
  <li>Clipboard Managers didn’t seem to really work.</li>
  <li>Lack of a Dash-like API lookup tool.</li>
</ul>

<p>There were some papercuts, too. These were the most painful:</p>
<ul>
  <li>Inability to configure the Framework 13’s trackpad scrolling speed.</li>
  <li>Global text replacement/text expansion doesn’t work</li>
  <li>PWA support with Firefox</li>
  <li>1Password reliability and compatibility with Firefox</li>
</ul>

<p>I’ve found solutions for some of these and set expectations about others, and have a decent working setup. It’s not perfect, but it’s usable.</p>

<h2 id="xremap-solves-keyboard-shortcuts">xremap Solves Keyboard Shortcuts</h2>

<p><a href="https://github.com/xremap/xremap"><code class="language-plaintext highlighter-rouge">xremap</code></a> seems to inject itself at the right layer of the OS to allow remapping keyboard shortcuts.  The best example of what was throwing me off was Firefox’s use of <kbd>Ctrl</kbd> as the modifier (and zero ability to change this setting).  My fingers are used to using the Cmd key on mac, <em>and</em> used to consistent shortcuts across apps (for example, <kbd>Ctrl</kbd>-<kbd>C</kbd> isn’t “copy” in terminal apps, since that sends a control sequence).</p>

<p><code class="language-plaintext highlighter-rouge">xremap</code> solves this, once I figured out the magic strings required to make it work and found a way to run it on login.  At a high level, you create a YAML file like so:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Firefox</span>
  <span class="na">application</span><span class="pi">:</span>
    <span class="na">only</span><span class="pi">:</span> <span class="s2">"</span><span class="s">firefox"</span>
  <span class="na">remap</span><span class="pi">:</span>
    <span class="na">Alt-q</span><span class="pi">:</span> <span class="s">C-q</span>
    <span class="na">Alt-c</span><span class="pi">:</span> <span class="s">C-c</span>
    <span class="na">Alt-v</span><span class="pi">:</span> <span class="s">C-v</span>
    <span class="na">Alt-t</span><span class="pi">:</span> <span class="s">C-t</span>
</code></pre></div></div>

<h3 id="figuring-out-magic-strings-is-hard">Figuring out Magic Strings is Hard</h3>

<p>The string given to <code class="language-plaintext highlighter-rouge">only:</code> is surprisingly hard to figure out.  Even now, the only way I know to figure it out is to run xremap with debugging and observe the log with what it outputs when you interact with a window (I’m running Wayland-only, and most of the internet searches for debugging this problem only work with X11).</p>

<p>But, the <code class="language-plaintext highlighter-rouge">remap:</code> section is where you set things up: when I type <kbd>Alt</kbd>-<kbd>c</kbd>, it sends <kbd>Ctrl</kbd>-<kbd>c</kbd>.  You can set this up for any app and it seems to work pretty consistently (in fact, this is probably the most consistent behavior of any piece of software on the Linux desktop).  You can even have it match wildcards, so here is how I apply all these settings to all Firefox PWAs:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">FirefoxPWA</span>
  <span class="na">application</span><span class="pi">:</span>
    <span class="na">only</span><span class="pi">:</span> <span class="s">/^FFPWA/</span>
  <span class="na">remap</span><span class="pi">:</span>
    <span class="na">Alt-q</span><span class="pi">:</span> <span class="s">C-q</span>
    <span class="na">Alt-c</span><span class="pi">:</span> <span class="s">C-c</span>
    <span class="na">Alt-v</span><span class="pi">:</span> <span class="s">C-v</span>
    <span class="na">Alt-t</span><span class="pi">:</span> <span class="s">C-t</span>
</code></pre></div></div>

<p>At this point, the main annoyance is having to set up a mapping for new apps I install or use.  But basically, if you are logging xremap’s output, you can click on an app, try to use a keyboard shortcut, and it will usually output that app’s name, which you can then use in this file.</p>

<p>For example, the file browser’s name is <code class="language-plaintext highlighter-rouge">org.gnome.Nautilus</code>, which I would never figured out in a million years.</p>

<h3 id="getting-things-to-run-at-login-is-hard">Getting Things to Run at Login is Hard</h3>

<p>It also helps to run <code class="language-plaintext highlighter-rouge">xremap</code> with <code class="language-plaintext highlighter-rouge">--watch</code> so it will pick up changes without having to be manually restarted.</p>

<p>I will say that getting this to run on login was amazingly difficult to do. I have no idea if what I came up with is the best way to do it, but it was the last of many attempts to get it working.  I had to make a wrapper script and much with <code class="language-plaintext highlighter-rouge">/etc/udev/rules.d/input.rules</code>.</p>

<p><a href="https://github.com/davetron5000/dotfiles/blob/main/linux-setup/0200-install-xremap.sh">Here is the script</a> for setting it up that I used.  Oof.</p>

<h2 id="clipboard-management-doesnt-really-work">Clipboard Management Doesn’t Really Work</h2>

<p>I tried a lot of clipboard managers, and ended up using <a href="https://github.com/Keruspe/GPaste">GPaste</a>. I wouldn’t say it works, but it seems to provide the easiest way to access previously-copied snippets.</p>

<p><strong>How Clipboard Management should work:</strong> Focus a text field in any app
(including vim), invoke a keyboard shortcut to show a menu of available
snippets on the clipoboard, select one, it is inserted into the text field.
This is how all clipboard management works on the Mac.</p>

<p><strong>How Clipboard Management works on Linux:</strong> Focus a text field in any app, but you
can’t have a global keyboard shortcut for whatever reason, so using the mouse
invoke the app and using the mouse select the snippet to paste, then use the
mouse to focus the text field again, then paste.  This sucks, but is workable.</p>

<p>From what I can tell, global keyboard shortcuts are difficult or impossible to
configure.  It also seems like there is not a universal text input system in
Linux, so the idea that an app can appear, be interacted with, go away, and
leave you back at the text input you started with is not possible to do
consistently.</p>

<p>I tried <em>a lot</em> of clipboard managers and <em>a lot</em> of different ways to invoke
them.  None of them truly worked, so I’ve settled for the workflow above. It’s
not great, but it works and is less friction than I would’ve thought.</p>

<h2 id="lack-of-api-lookup-app">Lack of API Lookup App</h2>

<p>To see the workflow for API lookups on the Mac, check out <a href="/blog/2025/03/21/one-week-with-desktop-linux-after-a-20-year-absence.html#api-documentation-lookup">my original post</a>.  It’s awesome to go from either Alfred (launcher) or vim to looking at API docs.</p>

<p>This just doesn’t seem possible on Linux.  I tried two solutions:</p>

<ul>
  <li><a href="https://zealdocs.org/">Zeal</a> is an attempt to build Dash
for Linux, but a) it is missing almost all documentation I would want, like
various RubyGems, and b) there is no way to programmatically invoke a search
such that it performs the search and focuses/activates the window.  This means
you have to use the mouse to interact with it.</li>
  <li><a href="https://devdocs.io">DevDocs</a> is a website that can be installed as a PWA.
You <em>can</em> programmatically perform a search and have it focus/activate by
properly and carefully configuring the PWA setup in Firefox. Unfortunately, a)
DevDocs has almost none of the documentation I want to look up (RubyGems), 
b) it’s extremely flaky, often showing no results or just hanging, and c) it
remembers the scroll position when loading new pages, meaning each new search
requires grabbing the mouse and scrolling back up to the top of the page.
Linux requires a <em>lot</em> of mousing.</li>
</ul>

<p>For now I’m using DevDocs and back to web searches for everything else.  This
really does suck, but I think I could live with it.</p>

<h2 id="do-not-use-snap-or-through-inaction-allow-snap-to-be-used">Do Not Use Snap or, through inaction, allow Snap to be used.</h2>

<p>I was banging my head against a wall for hours because I had installed Firefox and 1Password via Snap as that is what the OS was telling me to do.  In this setup, Firefox and 1Password do not work together. 1Password barely works at all, and Firefox PWAs do not work, period.</p>

<p>Unwinding this was not easy, despite using <code class="language-plaintext highlighter-rouge">snap</code> to uninstall them.  I had to first install these tools via apt, which was a huge amount of code and setup.  <em>Then</em> I was seeing snap just re-install these apps anyway.  So I had to do <a href="https://github.com/davetron5000/dotfiles/blob/main/linux-setup/0130-firefox.sh#L8">further munging</a> in some configuration to try to prevent that. I’m not sure if I have succeeded, but once I stopped using Snap, applications started working.</p>

<p>And, given how often Linux requires rebooting and logout/login, it seems to
have stuck to the Firefox/1Password versions I installed.</p>

<p><strong>Update While Writing This Post:</strong> Snap re-installed Firefox, even though I had
set the priority for it to not do that!  What a great fucking system!</p>

<p>I’m not sure what the point of Snap is, but my official policy is I hate it.</p>

<h2 id="pwas-sorta-work">PWAs Sorta Work!</h2>

<p>Progressive Web Apps AKA PWAs AKA running websites as if they were native apps are a really cool concept.  They really would be a <a href="https://www.youtube.com/watch?v=p1nwLilQy64">sweet solution</a> if they were properly supported by browsers and operating systems.  Chrome/Chromium/Edge appear to offer good support, but in practice, their PWAs don’t work - they are just windows of Chromium and not their own apps.  I think the reason has to do with something something Gnome something Wayland, but I have no idea. I tried <em>a lot</em> of configurations and Chrome-derived browsers. None worked property.</p>

<p>Ultimately, I was able to use the <a href="https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/">Progressive Web Apps for Firefox extension</a>, which provides the best support I could find.  The main issue with it is that it doesn’t really respect or support a lot of metadata and behavior for PWAs, so you have to muck in <code class="language-plaintext highlighter-rouge">about:config</code> to get things working well.</p>

<p>I was able to script this almost entirely, so the process of installing a PWA is as painless as I could make it.  But it still sucks.</p>

<p>The reason PWAs matter is that most services don’t make apps for Linux, but with a modicum of server-side configuration, and support from a browser/OS, a website can be made to act like an app.</p>

<p>I also created <a href="https://pwa.support">pwa.support</a> to try to document how PWAs
work across browsers and operating systems, plus details on how to use the PWA
for Firefox extension effectively.</p>

<h2 id="things-i-have-yet-to-solve">Things I have Yet To Solve</h2>

<h3 id="the-trackpad-sucks">The Trackpad Sucks</h3>

<p>The trackpad on the Framework 13 is absolute garbage for a lot of reasons, and two things make it hard to use that I couldn’t fix:</p>

<ul>
  <li>Clicking moves the mouse cursor so you get a lot of clicks in the wrong place.  Macs must have some way to counter this. The trackpad seems designed for a Windows world where you only ever click on the lower left part of the trackpad. Macs haven’t worked like that for over a decade and I cannot believe PC trackpads are still the same as they were 10 or even 20 years ago.</li>
  <li>Scrolling is hard-coded to the speed of light and not configurable that I could tell.  I think it has something to do with Wayland, but it’s just mind-boggling.  The scroll speed of my mouse is also not configurable, but it seems set at something usable.</li>
</ul>

<p>While I prefer a trackpad, and my Magic Trackpad does work, the scroll speed
makes them both unusable, so I try to use the mouse whenever I can’t use the
keyboard. Desktop Linux requires a ton of mousing, but it’s generally fine.</p>

<h3 id="fingerprint-doesnt-really-work">Fingerprint Doesn’t Really Work</h3>

<p>Using the Framework’s fingerprint to login pretty much doesn’t work.  About 50%
of the time, I open my laptop to a message that it’s timed out.  The remainder
of the time, if I don’t succeed on my first attempt it won’t try again.  I
set the PAM configuration to try more than once, but it just won’t.</p>

<p>For <code class="language-plaintext highlighter-rouge">sudo</code>  and 1Password unlocking it works more reliably, but also does not try more than once.  I did try to set up multiple fingerprints, but the UX is so bad. It took me 10 minutes of tapping my finger to get the first one set up.  The UI provides no real feedback about what’s happening or what to do.</p>

<h3 id="text-expansion-not-really-possible">Text Expansion Not Really Possible</h3>
<p>I don’t use a lot of text expansions, but I do have a few. I set up espanso and in some text fields in some apps when Mercury is in retrograde, it works.  All other times it doesn’t.  I think this is related to why clipboard management doesn’t work: there is no way to make a truly global keyboard shortcut and no API to interact with all text entry mechanisms in the OS.  Oh well.</p>

<h3 id="installation-hell">Installation Hell</h3>
<p>There are many ways to install software and they all require a lot of nonsense.  I’ve settled on using <code class="language-plaintext highlighter-rouge">apt</code> if possible, even if it requires <code class="language-plaintext highlighter-rouge">sudo cat whatever to /etc/whatever</code>.  I mean, <a href="https://github.com/davetron5000/dotfiles/blob/main/linux-setup/0610-terraform.sh">look at this nonsense</a> required to install Terraform!  WTF is any of that?  The only reason I trust it is because it’s from their website<sup id="back-1"><a href="#1">1</a></sup>.</p>

<p>Finding install instructions for software was not easy.  My general strategy
was to ask an AI, then use what it told me to find the actual instructions from
the vendor or developer’s website.  The AI was usually accurate, but given that
I have no fucking idea what <code class="language-plaintext highlighter-rouge">gpg --dearmor</code> does, I wasn’t about to trust it
outright.</p>

<p>I realize that each distro having at least one, if not three, package managers
makes this problem difficult for maintainers and vendors.  I also think
creating yet more package managers is not a solution. Ideally, there could be
some repository of “here’s how to install this software on this distro”
metadata that maintainers could provide, but that’s not how things work.</p>

<h3 id="notifications-are-cursed">Notifications are Cursed</h3>

<p>No app seems able to consistently maintain its own state and the state of its notifications, nor do they respond to clicks consistently.</p>

<p>For example, Ghostty allows apps to send notifications.  When this happens, I click the notification and…nothing happens.  Sometimes the notification is removed from the notification center, sometimes not. But clicking it never activates Ghostty.  Another example is Fastmail - it shows a badge and a notification for some number of unread messages. Sometimes that number is the exact number of unread messages! Frequently, it is not.</p>

<p>However notifications work under the hood, it’s either broken by design, or so complicated that no apps can figure out how to use it properly.  I would honestly prefer if apps just ran <code class="language-plaintext highlighter-rouge">xmessage</code> (not that you can programmatically activate and raise an app reliably [ and yes, I have the Gnome extension that claims to do that and it sometimes works! ])</p>

<h2 id="stuff-i-still-need-a-mac-for">Stuff I Still Need a Mac For</h2>
<p>I still use my Mac for a few things:</p>
<ul>
  <li>Apple Ecosystem stuff like iPhotos. These can be read from Linux via the iCloud website, but my iPhone photos go here.</li>
  <li>Right-click in Finder to scan documents from my Phone. OMG this is so useful, especially during the US’s tax season!</li>
  <li>iMessage - having to use my phone only for text messaging my friends sucks.</li>
  <li>Pixelmator and Omnigraffle are great apps.  I don’t use them often, but they work well.</li>
</ul>

<h2 id="where-i-go-from-here">Where I go From Here</h2>
<p>Because of <code class="language-plaintext highlighter-rouge">xremap</code> it’s in good enough shape that I can use this laptop without losing my mind and wreaking havoc with learned keyboard shortcuts.  I disabled a lot of pre-existing shortcuts and generally have a setup that isn’t hard to use.</p>

<p>As I mentioned in a previous post, I’m using AI code generators to understand how they work and how to use them, and doing this on a Linux box means the blast radius is contained.  There’s nothing on this laptop’s hard drive that matters.</p>

<p>But even beyond that, I am enjoying using it.  I realize there are other
distros and other window managers and all that.  I think for this second
attempt, since I wrote down everything I had to do to set stuff up, I’m more
confident in trying another window manager or distro<sup id="back-2"><a href="#2">2</a></sup>.  You can see <a href="https://github.com/davetron5000/dotfiles/tree/main/linux-setup">my setup scripts</a> in all their glory if you want to see what I’ve done.</p>

<p>For now, Desktop Linux <em>is</em> working well as a dev machine, despite my many complaints above.</p>

<hr />

<footer class="footnotes">
<ol>
<li id="1">
<sup>1</sup>I realize that all of this stuff is ensuring the integrity of the
packages being installed and that it's 100% more observable and auditable than
whatever Apple does to solve the same problem.  And I realize that the lack of
Linux standardization is both a benefit and a downside, but it
<strong>is</strong> just annoying to deal with.<a href="#back-1">↩</a>
</li>
<li id="2">
<sup>2</sup><strong>Do not</strong> let me know about Hyprland or Omakub or
Omarchy. As I said in a previous post, these people can all go fuck themselves.
I run a lot of software created by horrible people, or from companies run by 
horrible people. I get it. We can't live a life free of compromise. But I can
at least, for now, not use bullshit created by millionaire fascist race car
drivers. <a href="#back-2">↩</a>
</li>
</ol>
</footer>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[pwa.support and the Mediocre State of PWAs]]></title>
    <link href="https://naildrivin5.com/blog/2026/03/09/pwa-support-and-the-mediocre-state-of-pwas.html"/>
    <updated>2026-03-09T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2026/03/09/pwa-support-and-the-mediocre-state-of-pwas.html</id>
    <content type="html"><![CDATA[<p>I created <a href="https://pwa.support">pwa.support</a> as a way to both examine any website to see if it can be installed as
a progressive web app, but also to capture in some detail the depressing state of support for this concept across
major browsers and operating systems.</p>

<!-- more -->

<p>I’ve been revisiting desktop Linux since <a href="/blog/2025/03/21/one-week-with-desktop-linux-after-a-20-year-absence.html">my last attempt</a>, and it involved setting up a lot of
websites as Progressive Web Apps.  The state of platform and browser support for the <a href="https://www.youtube.com/watch?v=p1nwLilQy64">“sweet
solution”</a> is extremely mediocre.  There’s pretty much no combination
of operating system and browser that provides a truly app-like experience (except maybe Chromebooks).</p>

<p>I’ve captured it all on <a href="https://pwa.support">pwa.support</a>.  The content there is 100% written by me, after trying out various PWAs across Linux, macOS, Windows, and iOS (please get in touch if you can check this stuff on Android).  The site’s code, however, was “vibe coded”<sup id="back-1"><a href="#1">1</a></sup>.  I did this to try to understand how AI code generation tools work (see <a href="/blog/2026/02/23/the-death-of-the-software-craftsman.html">my post, “The Death of a Software Craftsman” for a breakdown of where I see things</a>).  I’m not “all-in” (per my post) on these tools, but felt it was important to understand how they work on something real.</p>

<p>I find “golly gee whiz guys, Claude Code did a thingy” posts extremely tedious. I will not bore you with my
specific thoughts and feelings.  They <em>are</em> captured on <code class="language-plaintext highlighter-rouge">pwa.support</code> if you want to read them, but I’m not
linking nor promoting it. I’m really tired of reading posts about how “amazing” these tools are. I’m no longer
amazed and so should you.</p>

<p>I do want to re-iterate, the content and opinions <em>on</em> <code class="language-plaintext highlighter-rouge">pwa.support</code> are 100% from me, based on me opening up
websites on a bunch of laptops.  Believe me, if I could never touch a Windows laptop again, I would. But I had to
see what the experience was like directly.  Sweet solution, it was not.</p>

<hr />

<footer class="footnotes">
<ol>
<li id="1">
<sup>1</sup>The code is not currently open source, primarily because I want you to treat it how I treated it - a
black box that you don't edit or look at.  TBH, the code is perfectly mediocre.<a href="#back-1">↩</a>
</li>
</ol>
</footer>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[The Death of the Software Craftsman]]></title>
    <link href="https://naildrivin5.com/blog/2026/02/23/the-death-of-the-software-craftsman.html"/>
    <updated>2026-02-23T12:30:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2026/02/23/the-death-of-the-software-craftsman.html</id>
    <content type="html"><![CDATA[<blockquote>
  <p>The death of a software craftsman<br />
Well, it happens a lot ‘round here<br />
You think quality is a common goal<br />
That goes to show how little you know</p>
</blockquote>

<p>Developers work hard over the years to cultivate tools and techniques to improve the quality of the construction of their software.  These tools and techniques are slowly becoming even more useless than they may already be. We must adapt by either going all-in, totally opting-out, or finding a middle ground by tracking the AI code-generation craze to fill the gap as a hand-coding artisan.</p>

<!-- more -->

<aside style="padding: 0.75rem; background-color: #eee; font-style: italic; font-size: 80%; margin-bottom: 1rem; border: solid thin black; border-radius: 0.25rem;">
All content on this page was <a href="https://declare-ai.org/1.0.0/none.html">created without any assistance from a Generative AI</a>
(including the Graphviz diagrams!). My
em-dashes are my own. I would also respectfully ask that you read it and not have an LLM summarize it.
</aside>

<h2 id="software-quality-problems-are-people-problems">Software Quality Problems are People Problems</h2>

<p>All techniques for improving the quality of software construction attempt to solve one problem: allow a person to understand the system they are changing so they can safely and correctly make a change to it.  Techniques either prevent certain classes of bugs (e.g. static typing, code review, web frameworks), or make it easier to manage complexity (e.g. object-orientation, microservices, function currying)<sup id="back-1"><a href="#1">1</a></sup>.</p>

<figure>
  <a href="/images/tradprog.png">
    <img src="/images/tradprog.png" srcset="/images/tradprog.png 629w,
                 /images/tradprog-320.png 320w,
                 /images/tradprog-500.png 500w" sizes="(max-width: 320px) 320px,
                (max-width: 500px) 500px,
                629px" alt="A flow chart showing a software development process that starts with three inputs: 'Any System', 'Description of Change', and 'Tools and Techniques for Quality Software Construction'. These feed into 'Developer', which outputs to 'Proposed Change'. This then feeds to a choice of 'Review'. One path out of 'Review' is 'needs revision', which goes back to 'Developer'. The other path out of 'Review' is to 'Updated System with Change'. This then leads to 'Observable Outcome'. There is a note pointing to 'Observable Outcome' that says 'This is all that really matters'" />
  </a>
  <figcaption class="">
    The Traditional Software Development Process (<a target="_new" href="/images/tradprog.png">Open bigger version in new window</a>)
  </figcaption>
</figure>

<p>Although we debate and discuss various techniques amongst ourselves, no non-programmer cares about them. They can’t even conceptualize how software is made, so they have no way to know what quality construction is, how to value it, or even how to identify it. They just care about outcomes.</p>

<blockquote class="pullquote">We tell ourselves that it's these skills that deliver those desired outcomes</blockquote>

<p>Despite all that, we tell ourselves that these skills are critical in delivering those desired outcomes. I myself have a very particular set of skills.  Skills I’ve acquired over a very long career.  I write about them, I use them, and I am truly convinced that careful construction of software is the best way to reliably create systems that can be easily changed over their lifetimes. Even if no one knows or cares.</p>

<p>However.</p>

<h2 id="best-block-no-be-there">Best Block No Be There</h2>

<p>I may relish the solutions to building quality software, but the best solution to any problem is to eliminate the problem. <a href="https://en.wikipedia.org/wiki/The_Karate_Kid">Don’t bet against Mr Miyagai’s wisdom</a>.</p>

<p>Let’s imagine a hypothetical tool that could take, as input, any software system and a description of a change. The tool produces, as output, an updated system with that change incorporated.  This hypothetical system would not rely on any particular software construction technique—it works with any system you give it.</p>

<figure>
  <a href="/images/aiblackbox.png">
    <img src="/images/aiblackbox.png" srcset="/images/aiblackbox.png 629w,
                 /images/aiblackbox-320.png 320w,
                 /images/aiblackbox-500.png 500w" sizes="(max-width: 320px) 320px,
                (max-width: 500px) 500px,
                629px" alt="A flow chart showing a software development process that starts with 'Any System' and 'Description of Change'. These feed into 'Black Box', which then leads to 'Updated System with Change'. This then leads to 'Observable Outcome'. There is a note pointing to 'Observable Outcome' that says 'This is all that really matters'" />
  </a>
  <figcaption class="">
    A Hypothetical Brave New Way to Make Software (<a target="_new" href="/images/aiblackbox.png">Open bigger version in new window</a>)
  </figcaption>
</figure>

<p>If software was created this way, would anyone know what, say, dependency injection was?  Would anyone go to a conference to learn about the latest features of Ruby on Rails?  Would anyone buy a book about carefully designing database schemas?</p>

<p>They would not. There would be no reason to.  It literally would not matter what the code was like. Our hypothetical system could handle whatever it’s given and produce the requested changes.  Sure, a small few would need to understand how code works, but the group would be quite small.</p>

<p>This hypothetical tool isn’t intended to be magic. Like any tool, there would be a skill to it, perhaps a difficult-to-master skill.  And
while there might be <em>some</em> overlap with the skills we use today to create quality software, most of <em>those</em> skills would become obsolete.</p>

<p>This system is not so hypothetical.</p>

<blockquote class="pullquote">We know that in at least some cases, they do exactly what I described above.</blockquote>

<p>We’ve seen what AI code generation systems are capable of.  We know that in at least some cases, they do exactly what I described above.  In some cases, they can take an existing system, a description of a change, and produce a system with that change.  And they seem to be improving quickly, handling more and more cases.</p>

<p>And yet.</p>

<h2 id="soylent-green-is-people">Soylent Green is People</h2>

<p>My visceral reaction to these tools has been a combination of disgust and boredom. Here are the things I have told myself about why this technology can or should be ignored:</p>

<ul>
  <li>It was created unethically.</li>
  <li>It consumes an unreasonable amount of resources (such as electricity and hard drives).</li>
  <li>It is owned and sold by some of the worst people in the world.</li>
  <li>Its true cost is hidden by investor money. Once its pricing is set in line with its true cost, no one could afford it.</li>
  <li>Being non-deterministic, it can never be as a good as a person.</li>
  <li>There’s no way to hold anyone accountable for its output.</li>
  <li>A real programmer will still need to go into the code. Practices around software construction will always matter.</li>
  <li>It’s shit at anything that’s not a popular technology applied to a common use case.</li>
</ul>

<p>As of this writing, these are all true.  But are they intrinsic problems? <em>Must</em> they be true, always?</p>

<blockquote class="pullquote">AI code generation's problems aren't actually as intrinsic as they may seem</blockquote>

<p>Unlike crypto, which is literally made of intrinsic problems preventing it from widespread adoption (please don’t email me), AI code generation’s problems aren’t actually as intrinsic as they may seem.</p>

<ul>
  <li>AI models <em>could</em> be created ethically. There could be (and perhaps are?) systems created that comply with the licenses of their training materials.</li>
  <li>Systems that use AI models <em>could</em> be done with less power and resources.  DeepSeek demonstrated that even minimal performance tweaking can give real benefits. Not enough to fully ameliorate the issues, but enough that I cannot say with certainty that these resource problems are unsolvable.</li>
  <li>AI code generation tools could be required to operate within a system of accountability.  We’ve all seen that CEOs will comply with the government when threatened.  And someday, we all might live under a functioning government that works for its people.</li>
  <li>Their price <em>may</em> be bearable when investor money runs out. Uber still exists as a going concern, despite being more expensive than ever.</li>
  <li>People are non-deterministic, too, so it’s not clear me that AI coding agents are necessarily worse than people at producing results. Coding agents are already better at programming than the vast majority of the population. I can’t say with certainty that every living programmer is the embodiment of <a href="https://en.wikipedia.org/wiki/John_Henry_(folklore)">John Henry</a>.</li>
  <li>While AI coding agents may never be able to handle every imaginable task, it’s not clear to me that there is a limit on what they can do or that any given limit is unreasonable. Most of us are putting spreadsheets in a web browser or wrapping a database in a front-end, so if all that work is automated, that doesn’t seem necessarily bad to me.</li>
</ul>

<p>In other words, all the problems of this technology <em>could</em> be addressed.  I’m not saying they will be, or that it will happen on any particular timeline.  But, it seems entirely possible to have a tool that produces software by taking in an existing system and written request as input, all without producing the externalities they currently do.</p>

<p>Not inevitable, but <em>possible</em>.</p>

<p>This means it’s worth considering a world where AI code generation is commonplace. In fact, professional software developers <em>must</em> consider such a world, and think deeply about their place in it.</p>

<p>As a thought experiment, imagine if all the issues above were addressed.  We have AI code generation tools are ethical, use appropriate resources, etc.  Who <em>wouldn’t</em> use these systems to produce software? At least in the context where the quality of the output was sufficient (a low bar if we are being honest), why would anyone <em>not</em> use these tools?</p>

<p>As much as I love coding, it would be really nice to produce results without fretting over variable names, modularity, OO purity, monads, or
any of that stuff.  Having a system produce reliable code just seems better. And it’s not like these tools are the first examples of code
generation—we use code generation all the time.</p>

<blockquote class="pullquote">Almost everyone in the orbit of software development only cares about outcomes</blockquote>

<p>Remember, almost everyone in the orbit of software development only cares about outcomes and results, not the process by which they were achieved.  It doesn’t mean <em>you</em> are wrong to care, but most people don’t.</p>

<p>So what’s a lonely programmer to do?</p>

<h2 id="ce-nest-pas-un-griefpost">Ce n’est Pas un Griefpost</h2>

<p>I’ve been a professional software developer for 30 years, and these AI code generation tools basically obviates a big chunk the skills I’ve developed. But I <em>like</em> using those skills. I like writing code. I like the process of building software. How do I navigate a world that no longer values that?</p>

<p>I’m results-oriented and am good at communicating with non-programmers. I can manage teams and projects, and keep people focused on business outcomes.  These are all skills you need no matter how code is written. But my least happy professional eras were when I wasn’t involved with code.</p>

<p>Up until now, the never-ending need for programmers has given me a nice career where I can balance all of this stuff.  Thankfully, I’m on the tail end of that career.  But I’m not retired yet.</p>

<p>I see three paths in front of me: Hard Pass, All In, and Embrace Tradition.</p>

<h3 id="hard-pass">Hard Pass</h3>

<p>The problems I listed above are real.  And while they could be solved, there’s no guarantee they will be solved in any given timeframe, or even at all.  The problems compound and create what many believe to be an easy choice: don’t support unethical systems created by terrible people that quickly exhaust our resources.</p>

<p>This is how I felt initially. It’s just easy to write this entire thing off as awful, useless, evil, of poor quality, and not something I want to be involved with (like crypto!). Unlike ignoring crypto, the Hard Pass has consequences to the career of a
software professional.</p>

<p>In the short term, it’s still possible to be gainfully employed in software while abstaining from AI.  There’ll be fewer and fewer such jobs as time goes by, but there should be at least a few years of generally available jobs writing code by hand.</p>

<p>But these positions will become fewer and far between.  Choosing AI abstinence means ultimately exiting professional software development, at least at some point.</p>

<blockquote class="pullquote">Choosing AI abstinence means exiting professional software development</blockquote>

<p>This might seem extreme, but be honest: these tools will become far more prolific over the short term.  Not enough people are going to come around on the ethical issues to slow or stop their adoption. The country I live in is 350+ million people who tolerate the sexual exploitation and murder of children (as just one example).  I say this not to encourage you to give in or sell out, but just to understand that the world of software development as you know it is going to become very small very fast.</p>

<p>This is the consequence of the moral dilemma.  You <em>can</em> opt-out. Almost every job that ever was or ever will be is something other than writing software. Most people make a living without writing software.  But if you want to give AI a Hard Pass, you will eventually be giving your career as a programmer a hard pass, too (though please stick around for option three, below).</p>

<p>If this is you, start figuring out how you’re going to make a living otherwise.</p>

<p>Or, you could go all-in.</p>

<h3 id="all-in">All In</h3>

<p>Going all-in is to accept that the profession is changing so radically that you’ll
need to retrain yourself.  You’ll leverage your experience and incorporate these new
tools in the same way as you would any other advance in the field. Except you may
leave a lot of your existing skills behind.</p>

<blockquote class="pullquote">It's hard to live a life free of compromise</blockquote>

<p>It’s hard to live a life free of compromise. Going All In is to compromise. It means you must tolerate the downsides of this technology (assuming you believe there <em>are</em> at least some downsides). Of course, tolerance is not the same thing as support. Every one, every where, every day must weigh their needs against what their conscience can bear.</p>

<p>If your priority is to stay working in software development, especially if you have a long career ahead of you, going All In is the safest, simplest, most practical option. You just have to be able to live with yourself.</p>

<p>There are two reasons I find this option depressing, beyond having to tolerate the ethical problems.</p>

<figure class="small-figure left">
  <a href="/images/prettyplease.jpg">
    <img src="/images/prettyplease.jpg" alt="A picture of Harvey Keitel's character 'The Wolf' from Pulp Fiction with the text 'Pretty Please with Sugar On Top Validate the Fucking Email' overlayed" />
  </a>
</figure>

<p>One is as I mentioned above: I <em>like</em> coding. I like writing code and everything about it.  I like the control it gives me, and I do not enjoy “coding” by writing Markdown and feeding it to a compiler that sometimes works and sometimes doesn’t until I ask it nicely to do what I need.</p>

<div class="cf"></div>

<figure class="small-figure right">
  <a href="/images/i-like-coding.jpg">
    <img src="/images/i-like-coding.jpg" alt="A four-panel meme comic showing me in panel one saing 'I like coding'. Panel 2 shows someone else saying 'Here's some React and Tailwind'. The third panel just shows me with no text. The fourth panel is me looking angry" />
  </a>
</figure>

<p>Two is related to the “common technology” problem.  These tools produce code using the popular frameworks, techniques, and libraries.  While it’s possible I won’t have to review or modify the code these tools produce some day, today is not that day. And I just really, really don’t want to write React or Tailwind.  I don’t really want to learn Python.  And the creator of Rails can go fuck himself.</p>

<div class="cf"></div>

<p>But that’s me.  If I were younger, smarter, and less concerned with the ethical
issues, I’d embrace this new world, and not worry so much about what code was being
produced. The whole point is that it won’t matter in the end.</p>

<p>All this being said, flat-pack furniture made of fiberboard did not eliminate the skill of putting a chisel to hard wood. The time of the software craftsman could be coming.</p>

<h2 id="reject-modernity-embrace-tradition">Reject Modernity, Embrace Tradition</h2>

<p>I always thought the “software craftsman” movement was dumb. Uncle Bob is a terrible person with bad ideas, and Agile Thought Leaders seem more focused on their billable rate than improving outcomes for users.  Their collective disrepsect for anyone not a programmer is galling.  But most of all, it just seemed like navel-gazing.  Results and outcomes <em>really do</em> matter!</p>

<figure class="small-figure left image-border">
  <a href="/images/tradition.jpg">
    <img src="/images/tradition.jpg" alt="A two panel meme with the top panel titled 'Reject Modernity' and contents of the text '&gt; npm install -g typescript-language-server typescript'. The bottom panel is titled 'Embrace Tradition' and as the text 'hjkl' in it" />
  </a>
</figure>

<p>But what is a craftsman, really? Someone with deep skills honed over many years, who can produce amazing results using a process that’s almost as engaging to observe as the results themselves.  There are certain outcomes that only a highly trained human hand can deliver.</p>

<div class="cf"></div>

<p>There are still craftsmen being trained and employed to this day, across a wide variety of industries.  Although few people have hand-made furniture, you can still commission it. And don’t forget that even uninspiring chain restaurants like The Cheesecake Factory still make almost all their food from scratch.</p>

<p>Thus, it’s not unreasonable to think that such an industry will exist for writing software.  In the short term, there’s still a ton that AI coding agents simply can’t do very well.  And in the long term, there will be at least some demand for software written to a higher standard than what AI is producing. Of course, there will always be the need to pay a high price to have real programmer pop the hood on your vibe-coded mess to figure out what’s actually wrong.</p>

<p>However, to live in this world as a Software Crafter<sup id="back-2"><a href="#2">2</a></sup> is to understand AI code generation…at least until it plateaus in capabilities. To be marketable and make a living, you have to know what gap you are filling. And that gap is changing often.  Thus, you cannot abstain entirely from AI if this is the way you go.  You may not use it to produce your results, but you will need to use it—not just read about it—to understand it.</p>

<p>We don’t get to live our lives free of compromise.</p>

<p>Maybe in the next world.</p>

<hr />

<footer class="footnotes">
<ol>
<li id="1">
<sup>1</sup>I'm not saying stuff like static typing and object-orientation achieve the results they think promise they do, especially in a general sense, but they intend to prevent bugs, manage complexity, and allow easier changes to software.<a href="#back-1">↩</a>
</li>
<li id="2">
<sup>2</sup>In  this new era, we can ditch the gendered language. "Craftsperson" is cumbersome, but would also work. "Maker" can go straight to hell.<a href="#back-2">↩</a>
</li>
</ol>
</footer>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Discussing Brut on Dead Code Podcast]]></title>
    <link href="https://naildrivin5.com/blog/2025/11/05/discussing-brut-on-dead-code-podcast.html"/>
    <updated>2025-11-05T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2025/11/05/discussing-brut-on-dead-code-podcast.html</id>
    <content type="html"><![CDATA[<p>I recently got to chat with <a href="https://ruby.social/@jardo">Jared Norman</a> on the <a href="https://shows.acast.com/dead-code/episodes/brut-al-death-with-david-bryant-copeland">Dead Code Podcast</a>. We talked mostly about Brut, but also a bit about hardware synthesizers and <a href="https://bandwagon.fm/68e6bf0df1f8302aa576c863">looptober</a>.</p>

<p>If you want to know about more about why Brut exists or its philisophical underpinnings, check it out!</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Building a Sub-command Ruby CLI with just OptionParser]]></title>
    <link href="https://naildrivin5.com/blog/2025/10/07/building-a-sub-command-ruby-cli-with-just-optionparser.html"/>
    <updated>2025-10-07T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2025/10/07/building-a-sub-command-ruby-cli-with-just-optionparser.html</id>
    <content type="html"><![CDATA[<p>I’ve <a href="https://www.amazon.com/Build-Awesome-Command-Line-Applications-Ruby/dp/1934356913">thought deeply about building CLIs</a> and built a
<em>lot</em> of them over the years.  I’ve used Rake, Thor, my own gem GLI and many others.  After all that, the venerable <code class="language-plaintext highlighter-rouge">OptionParser</code>—part of
Ruby’s standard library—is the best choice for scripting and sub-command (git-like) CLIs.  I want to show you how.</p>

<!-- more -->

<h2 id="what-is-a-sub-command-cli">What is a Sub-Command CLI?</h2>

<p>At first glance, <code class="language-plaintext highlighter-rouge">OptionParser</code> doesn’t seem to support a sub-command CLI, like so (I’ll explain what each part is below):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; bin/test --verbose audit --type Component specs/front_end
</code></pre></div></div>

<p>Yes, you could configure <code class="language-plaintext highlighter-rouge">--verbose</code> and <code class="language-plaintext highlighter-rouge">--type TYPE</code>, then figure out that the first thing left over in <code class="language-plaintext highlighter-rouge">ARGV</code> was a command, but it gets very cumbersome when things get beyond trivial, especially when you want to show help.</p>

<p>Fortunately, <code class="language-plaintext highlighter-rouge">OptionParser's</code> lesser-known (and oddly-named) method
<a href="https://docs.ruby-lang.org/en/3.4/OptionParser.html#method-i-order-21"><code class="language-plaintext highlighter-rouge">order!</code></a> parses the command-line up to the first argument
it doesn’t understand.  How does this help?</p>

<p>Consider the command above.  It’s made up of five parts: the app name (<code class="language-plaintext highlighter-rouge">bin/test</code>), the globally-applicable options (<code class="language-plaintext highlighter-rouge">--verbose</code>), the sub
command (<code class="language-plaintext highlighter-rouge">audit</code>), command-scoped options (<code class="language-plaintext highlighter-rouge">--type Component</code>) and the arguments (<code class="language-plaintext highlighter-rouge">specs/front_end</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; bin/test --verbose audit --type Component specs/front_end
   ---+--     ---+-- --+--   ----------+---  ----+------
      |          |     |               |         |
App---+          |     |               |         |
Global Options---+     |               |         |
Sub Command------------+               |         |
Command Options------------------------+         |
Arguments----------------------------------------+
</code></pre></div></div>

<p>You’d design a CLI like this if the various sub-commands had shared code or behavior. It also helps to avoid having a zillion different
scripts and provides a namespace, especially if you can provide good help. Fortunately, <code class="language-plaintext highlighter-rouge">OptionParse</code> <em>does</em> provide good help and can parse this.</p>

<h2 id="two-option-parsers-divide-up-the-work">Two Option Parsers Divide Up the Work</h2>

<p>The key is to use <em>two</em> <code class="language-plaintext highlighter-rouge">OptionParsers</code>:</p>

<ul>
  <li>The first parses the global options. It uses <code class="language-plaintext highlighter-rouge">order!</code> (instead of <code class="language-plaintext highlighter-rouge">parse!</code>) so that it only parses options up to the first argument it
doesn’t understand (<code class="language-plaintext highlighter-rouge">audit</code>) in our case.</li>
  <li>The second uses <code class="language-plaintext highlighter-rouge">parse!</code>, which consumes the entire rest of the command line, leaving <code class="language-plaintext highlighter-rouge">ARGV</code> with whatever wasn’t parsed.</li>
</ul>

<p>Here’s a basic sketch.  First, we’ll create the global <code class="language-plaintext highlighter-rouge">OptionParser</code>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s2">"optparse"</span>

<span class="n">global_parser</span> <span class="o">=</span> <span class="no">OptionParser</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">opts</span><span class="o">|</span>
  <span class="n">opts</span><span class="p">.</span><span class="nf">banner</span> <span class="o">=</span> <span class="s2">"bin/test [global options] command [command options] [command args...]"</span>
  <span class="n">opts</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="s2">"--verbose"</span><span class="p">,</span> <span class="s2">"Show additional logging/debug information"</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Next, we’ll need the second <code class="language-plaintext highlighter-rouge">OptionParser</code> for the <code class="language-plaintext highlighter-rouge">audit</code> subcommand.  You’d need one <code class="language-plaintext highlighter-rouge">OptionParser</code> for each subcommand you want to
support.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">commands</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">commands</span><span class="p">[</span><span class="s2">"audit"</span><span class="p">]</span> <span class="o">=</span> <span class="no">OptionParser</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">opts</span><span class="o">|</span>
  <span class="n">opts</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="s2">"--type TYPE"</span><span class="p">,</span> <span class="s2">"Set the type of test to audit. Omit to audit all types"</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Add more OptionParsers for more commands as needed</span>
</code></pre></div></div>

<p>Now, when the app runs, we parse the global options first using <code class="language-plaintext highlighter-rouge">order!</code>. This means that <code class="language-plaintext highlighter-rouge">ARGV[0]</code> (i.e. the first part of the command line that didn’t match anything in the global <code class="language-plaintext highlighter-rouge">OptionParsers</code>) is the command name. We use that to locate the <code class="language-plaintext highlighter-rouge">OptionParser</code> to use, then call <code class="language-plaintext highlighter-rouge">parse!</code> on that.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">global_options</span>  <span class="o">=</span> <span class="p">{}</span>
<span class="n">command_options</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">global_parser</span><span class="p">.</span><span class="nf">order!</span><span class="p">(</span><span class="ss">into: </span><span class="n">global_options</span><span class="p">)</span>
<span class="n">command</span> <span class="o">=</span> <span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">command_parser</span> <span class="o">=</span> <span class="n">commands</span><span class="p">[</span><span class="n">command</span><span class="p">]</span>
<span class="n">command_parser</span><span class="p">.</span><span class="nf">parse!</span><span class="p">(</span><span class="ss">into: </span><span class="n">command_options</span><span class="p">)</span>

<span class="c1"># Now, based on the value of command, do whatever needs doing</span>
</code></pre></div></div>

<p>What <code class="language-plaintext highlighter-rouge">OptionParser</code> doesn’t give you is a way to manage the code to run for the e.g. <code class="language-plaintext highlighter-rouge">audit</code> command, but you have all the object-oriented
facilities of Ruby available to do that.  In <a href="https://brutrb.com">Brut</a>, the way I did this was to create a class with an <code class="language-plaintext highlighter-rouge">execute</code> method
that maps to its name and exposes it’s <code class="language-plaintext highlighter-rouge">OptionParser</code>.  Roughly:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">class</span> <span class="nc">AuditCommand</span>

  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">option_parser</span>
    <span class="no">OptionParser</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">opts</span><span class="o">|</span>
      <span class="n">opts</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="s2">"--type TYPE"</span><span class="p">,</span> <span class="s2">"Set the type of test to audit. Omit to audit all types"</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">command_options</span><span class="p">:,</span> <span class="n">args</span><span class="p">:)</span>
    <span class="vi">@command_options</span> <span class="o">=</span> <span class="n">command_options</span>
    <span class="vi">@args</span>            <span class="o">=</span> <span class="n">args</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">execute</span>
    <span class="c1"># whatever</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">commands</span><span class="p">[</span><span class="s2">"audit"</span><span class="p">]</span> <span class="o">=</span> <span class="no">AuditCommand</span>

<span class="c1"># ...</span>
<span class="n">command</span> <span class="o">=</span> <span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">command_klass</span> <span class="o">=</span> <span class="n">commands</span><span class="p">[</span><span class="n">command</span><span class="p">]</span>
<span class="n">command_parser</span> <span class="o">=</span> <span class="n">command_klass</span><span class="p">.</span><span class="nf">option_parser</span>
<span class="n">command_parser</span><span class="p">.</span><span class="nf">parse!</span><span class="p">(</span><span class="ss">into: </span><span class="n">command_options</span><span class="p">)</span>
<span class="n">command_klass</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">command_options</span><span class="p">:,</span> <span class="ss">args: </span><span class="no">ARGV</span><span class="p">).</span><span class="nf">execute</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">OptionParser</code> also provides sophisticated type coercion via <a href="https://docs.ruby-lang.org/en/3.4/OptionParser.html#method-i-accept"><code class="language-plaintext highlighter-rouge">accept</code></a>.
<a href="https://docs.ruby-lang.org/en/3.4/OptionParser.html#class-OptionParser-label-Type+Coercion">Many built-in conversions are available</a> and you
can <a href="https://docs.ruby-lang.org/en/3.4/OptionParser.html#class-OptionParser-label-Creating+Custom+Conversions">create your own</a>.</p>

<p>This code gets more complex when you want to show help or handle errors</p>

<h2 id="showing-help-and-handling-errors">Showing Help and Handling Errors</h2>

<p><code class="language-plaintext highlighter-rouge">OptionParser</code> can produce a decent help message:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">puts</span> <span class="n">global_parser</span>
<span class="c1"># or</span>
<span class="nb">puts</span> <span class="n">command_parser</span>
</code></pre></div></div>

<p>You can do much fancier stuff if needed by using <a href="https://docs.ruby-lang.org/en/3.4/OptionParser.html#method-i-summarize"><code class="language-plaintext highlighter-rouge">summarize</code></a>.</p>

<p>For handling errors, <code class="language-plaintext highlighter-rouge">OptionParser</code> will raise an error if options were provided that aren’t valid, and you can check whatever you need and
call <code class="language-plaintext highlighter-rouge">exit</code>.</p>

<p>Now, there is a lot of “do whatever you want” here, as well as potentially verbose code.  Why not use a gem that does this for you?</p>

<h2 id="dont-use-gems-if-your-needs-are-typical">Don’t Use Gems if Your Needs are Typical</h2>

<div data-ad=""></div>

<p>Code that relies only on the standard library is stable code.  The standard library rarely breaks things and is maintained.  <code class="language-plaintext highlighter-rouge">OptionParser</code>
is designed to parse a UNIX-like command line, which is usually  what you want.</p>

<p>Even though <code class="language-plaintext highlighter-rouge">OptionParser</code> is a bit verbose, you likely aren’t writing command-line code frequently, so the verbosity—and reliance on the
standard library—is a bonus.  DSLs, at least in my experience, tend to have a half-life and can be hard to pickup, so you re-learn them over
and over, unless you are working in them every day.</p>

<p>I built <a href="https://github.com/davetron5000/gli">GLI</a> to make this easier, but in practice, it’s a somewhat wide DSL that you have to re-learn
when editing your CLI.</p>

<p><a href="https://github.com/rails/thor">Thor</a> is very popular, included with Rails, and mostly supports this kind of UI, but it is an even denser DSL that I don’t think rewards you for learning it.  And, because it does not use <code class="language-plaintext highlighter-rouge">OptionParser</code>, it’s very sensitive to command and argument ordering in a way that seasoned UNIX people would find surprising and annoying.  It also includes a ton of other code you likely don’t need, such as the ability copy files and templates around.</p>

<p><a href="https://github.com/ruby/rake">Rake</a> <em>is</em> part of the standard library, but the CLIs it produces are not very ergonomic. You must use a
sequence of square brackets and quotes to pass arguments, and there is no facility for options like <code class="language-plaintext highlighter-rouge">--verbose</code>.  Rake is designed as a
dependency manager, e.g. build my <code class="language-plaintext highlighter-rouge">favicon.ico</code> whenever my <code class="language-plaintext highlighter-rouge">favicon.png</code> changes. It’s not a general-purpose way to make command line apps.</p>

<p>So, embrace the standard library, and embrace <code class="language-plaintext highlighter-rouge">OptionParser</code>!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Confirmation Dialog with BrutRB, Web Components, and no JS]]></title>
    <link href="https://naildrivin5.com/blog/2025/08/18/confirmation-dialog-with-brutrb-web-components-and-no-js.html"/>
    <updated>2025-08-18T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2025/08/18/confirmation-dialog-with-brutrb-web-components-and-no-js.html</id>
    <content type="html"><![CDATA[<p>I created <a href="https://video.hardlimit.com/w/4y8Pjd8VVPDK372mozCUdj">a short (8 minute) screencast</a> on adding a confirmation dialog to form submissions using <a href="https://brutrb.com">BrutRB</a>’s bundled Web Components. You don’t have to write any JavaScript, and you can completely control the look and feel with CSS.</p>

<iframe title="Add A Confirmation Dialog in Brut with Zero JS in like 8 Minutes" width="560" height="315" src="https://video.hardlimit.com/videos/embed/4y8Pjd8VVPDK372mozCUdj" frameborder="0" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe>

<p>There’s also a <a href="https://brutrb.com/tutorials/02-dialog.html">tutorial</a> that does the same thing or, if you are super pressed for time:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;form&gt;</span>
  <span class="nt">&lt;brut-confirm-submit</span> <span class="na">message=</span><span class="s">"Are you sure?"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;button&gt;</span>Save<span class="nt">&lt;/button&gt;</span>
  <span class="nt">&lt;/brut-confirm-submit&gt;</span>
<span class="nt">&lt;/form&gt;</span>

<span class="nt">&lt;brut-confirmation-dialog&gt;</span>
  <span class="nt">&lt;dialog&gt;</span>
    <span class="nt">&lt;h1&gt;&lt;/h1&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">value=</span><span class="s">"ok"</span><span class="nt">&gt;&lt;/button&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">value=</span><span class="s">"cancel"</span><span class="nt">&gt;</span>Nevermind<span class="nt">&lt;/button&gt;</span>
  <span class="nt">&lt;/dialog&gt;</span>
<span class="nt">&lt;/brut-confirmation-dialog&gt;</span>
</code></pre></div></div>

<p>Progressive enhancement, and no magic attributes on existing elements.</p>

<ul>
  <li><a href="https://brutrb.com/brut-js/api/ConfirmSubmit.html"><code class="language-plaintext highlighter-rouge">&lt;brut-confirm-submit&gt;</code> docs</a></li>
  <li><a href="https://brutrb.com/brut-js/api/ConfirmationDialog.html"><code class="language-plaintext highlighter-rouge">&lt;brut-confirmation-dialog&gt;</code> docs</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Please Create Debuggable Systems]]></title>
    <link href="https://naildrivin5.com/blog/2025/08/06/please-create-debuggable-systems.html"/>
    <updated>2025-08-06T14:55:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2025/08/06/please-create-debuggable-systems.html</id>
    <content type="html"><![CDATA[<p>When a system isn’t working, it’s far easier to debug the problem when that system produces good error messages <em>as well as useful diagnostics</em>. Silent failures are sadly the norm, because they are just easier to implement.  Systems based on conventions or automatic configuration exacerbate this problem, as they tend to just do nothing and produce no error message. Let’s see how to fix this.</p>

<!-- more -->

<p>Rails popularized “convention over configuration”, but it often fails to help when conventions aren’t aligned, often silently failing with no help for debugging. This cultural norm has proliferated to many Ruby tools, like Shopify’s ruby-lsp, and pretty much all of Apple’s software design.</p>

<ul>
  <li>I asked my editor to jump to a definition and the LSP didn’t do it and there is no error message.</li>
  <li>I took a picture on my phone, it’s connected to WiFi, as is my computer, and it’s not synced to my photos. There is no “sync” button, nor
any sort of logging telling me if it tried to sync and failed or didn’t try and why not.</li>
  <li>I’m creating my dev and test databases and <a href="https://stackoverflow.com/questions/50720730/rails-env-development-rake-dbcreate-is-not-creating-development-database">it doesn’t create my dev database, but creates my test database twice</a>. (I hope this poor guy figured it out…it’s been seven years!)</li>
</ul>

<p>We all experience these failures where we get an error message that’s not helpful and then no real way to get more information about the problem.</p>

<p>Creating a debuggable system is critical for managing software, especially now that more and more code is not written by a real person.  To create such a system, it must provide two capabilities:</p>

<ul>
  <li>Helpful and descriptive error messages</li>
  <li>The ability to ask the system for much more detailed information</li>
</ul>

<p><em>Both</em> of these capabilities must be pre-built into the system. They cannot be provided only in some interactive debugging session or only in a development environment.  You want these capabilities in your <em>production</em> system.</p>

<h2 id="write-helpful-and-descriptive-error-messages">Write Helpful and Descriptive Error Messages</h2>

<p>There is always a tension between an error message that is so full of information as to be useless and one so vacant that it, too, is useless.  Designers never want users to see error messages. The security team never wants to allow error messages to provide hackers with information.  And programmers often write errors in their own language, which no one else understands.</p>

<p>Ideally, each error message the system produces is both <em>unique</em> and is written in a way you can <em>reference</em> more detailed information about what to do.</p>

<p>Consider what happens when using NeoVim and Shopify’s ruby-lsp is asked to go to the definition of a Ruby class and, for whatever reason, it can’t:</p>

<blockquote>
  <p>No Location Found</p>
</blockquote>

<p>This absolutely sucks:</p>

<ul>
  <li>It doesn’t explain what went wrong</li>
  <li>It doesn’t provide any pointers for further investigation</li>
  <li>It’s not clear what is producing this message: ruby-lsp, the Neovim plugin, or Neovim itself</li>
  <li>It doesn’t even say what operation it was trying to perform!</li>
</ul>

<p>Here are some better options:</p>

<ul>
  <li>“Could not find definition of ‘FooComponent’”</li>
  <li>“Could not find definition of ‘FooComponent’, ruby-lsp returned empty array”</li>
  <li>“Could not find definition of ‘FooComponent’, restart ruby-lsp server with --debug to debug”</li>
  <li>“Could not find definition of ‘FooComponent’, see NeoVim’s log at ~/cache/logs/neovim.log for details”</li>
  <li>“Could not find definition of ‘FooComponent’, searched 1,234 defined classes from 564 folders”</li>
</ul>

<p>These messages each have attributes of a useful error:</p>

<ul>
  <li>The operation that caused the issue (“Could not find definition”)</li>
  <li>The specific inputs to that operation (<code class="language-plaintext highlighter-rouge">FooComponent</code>)</li>
  <li>Observed behavior of dependent systems (“ruby-lsp returned empty array”)</li>
  <li>Options to get more information (“restart ruby-lsp…” and “see NeoVim’s log”)</li>
  <li>Metadata about the request (“searched 1,234 classes…”)</li>
</ul>

<p>These can all help you try to figure out the problem.  Even if you can’t provide <em>all</em> diagnostics, you should always consider including in your error message:</p>

<ul>
  <li>What operation you tried to perform</li>
  <li>What result you got (<em>summarized</em>, not <em>analyzed</em>)</li>
  <li>What systems are involved:
    <ul>
      <li>attach your system’s name to messages you create</li>
      <li>attach the subsystem’s name to message you receive and pass along</li>
    </ul>
  </li>
</ul>

<p>Aside from this, creating a way to get more information is also extremely helpful.</p>

<h2 id="create-a-debug-or-diagnostic-mode">Create a Debug or Diagnostic Mode</h2>

<p>The volume of information required to fully debug a problem can be quite large.  It can be costly to produce and difficult to analyze. This can be a worthwhile tradeoff if something isn’t working and you don’t have any other options.  This means your system needds a <em>debug</em> or <em>diagnostic</em> mode.</p>

<p>A diagnostic mode should produce the inputs and outputs as well as intermediate values relevant to producing the outputs. Let’s imagine how finding the definition of a class in Ruby works in the ruby-lsp.</p>

<p>At a high level, the inputs are the symbol being looked-up and the outputs are a list of files and locations where that sybmol is defined.  LSP is more low level, however, as it will actually accept as input a line/column of a file where a symbol is referenced, and expect a list similar locations in return.</p>

<p>This means there are a few ways this can fail:</p>

<ul>
  <li>The file doesn’t exist</li>
  <li>There is no symbol at the location of the file</li>
  <li>The symbol’s definition can’t be found</li>
  <li>The symbol was found, but the file isn’t accessible to the caller</li>
</ul>

<p>The most common case is a symbol being correctly identified in the file, but not found. This is where intermediate values can help.</p>

<div data-ad=""></div>

<p>Presumably, a bunch of files were searched for the symbol that can’t be found.  Knowing those files would be useful!  But, presumably, those files were found by searching some list of folders for Ruby files. <em>That</em> list of folders would be nice to know as well!</p>

<p>This is obviously a massive amount of information for a single-line error message, however the information could be stored.  The entire operation could be given a unique ID, which is then included in the error message <em>and</em> included in a log file that produces all of this information.  Given the volume of information, you’d probably want the LSP to only produce this when asked, either with a per-request flag or a flag at startup (e.g. <code class="language-plaintext highlighter-rouge">--diagnostic</code> or <code class="language-plaintext highlighter-rouge">--debug</code>).</p>

<p>Making all this avaiable requires extra effort on the part of the programmer. Sometimes, it could be quite a bit of effort!  For example, there may not be an easy way to generate a unique ID and ensure it’s available to everywhere in the code with access to the diagnostic information.  And, of course, all this diagnostic code can itself fail, creating <em>more</em> intermediate values needed to diagnose problems.  We’ve probably all written something like this before:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">begin</span>
  <span class="n">some_operation</span>
<span class="k">rescue</span> <span class="o">=&gt;</span> <span class="n">ex</span>
  <span class="k">begin</span>
    <span class="n">report_error</span><span class="p">(</span><span class="n">ex</span><span class="p">)</span>
  <span class="k">rescue</span> <span class="o">=&gt;</span> <span class="n">ex2</span>
    <span class="vg">$stderr</span><span class="p">.</span><span class="nf">puts</span> <span class="s2">"Encountered </span><span class="si">#{</span><span class="n">ex2</span><span class="si">}</span><span class="s2"> while reporting error </span><span class="si">#{</span><span class="n">ex</span><span class="si">}</span><span class="s2"> - something is seriously wrong"</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>In addition to just culling the data, you have to log it, or not.  Ruby’s <code class="language-plaintext highlighter-rouge">Logger</code> provides a decent solution using blocks:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logger</span><span class="p">.</span><span class="nf">debug</span> <span class="p">{</span>
  <span class="c1"># expensive calculation</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The block only executes if the logger is set to debug level.  Of course, you may not like the all-or-nothing approach.  The venerable log4j used in almost every Java app allows you to configure the log level <em>per class</em> and even dynamically change it at runtime. You can do this in Ruby with <a href="https://logger.rocketjob.io/">SemanticLogger</a>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s2">"semantic_logger"</span>

<span class="no">SemanticLogger</span><span class="p">.</span><span class="nf">appenders</span> <span class="o">&lt;&lt;</span> <span class="no">SemanticLogger</span><span class="o">::</span><span class="no">Appender</span><span class="o">::</span><span class="no">IO</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">STDOUT</span><span class="p">)</span>

<span class="k">class</span> <span class="nc">Foo</span>
  <span class="kp">include</span> <span class="no">SemanticLogger</span><span class="o">::</span><span class="no">Loggable</span>
  <span class="k">def</span> <span class="nf">doit</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">debug</span><span class="p">(</span><span class="s2">"FOO!"</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">class</span> <span class="nc">Bar</span>
  <span class="kp">include</span> <span class="no">SemanticLogger</span><span class="o">::</span><span class="no">Loggable</span>
  <span class="k">def</span> <span class="nf">doit</span>
    <span class="n">logger</span><span class="p">.</span><span class="nf">debug</span><span class="p">(</span><span class="s2">"BAR!"</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">foo</span> <span class="o">=</span> <span class="no">Foo</span><span class="p">.</span><span class="nf">new</span>
<span class="n">bar</span> <span class="o">=</span> <span class="no">Bar</span><span class="p">.</span><span class="nf">new</span>

<span class="n">foo</span><span class="p">.</span><span class="nf">debug</span> <span class="c1"># =&gt; nothing</span>
<span class="n">bar</span><span class="p">.</span><span class="nf">debug</span> <span class="c1"># =&gt; nothing</span>
<span class="no">Foo</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">level</span> <span class="o">=</span> <span class="ss">:debug</span>
<span class="n">foo</span><span class="p">.</span><span class="nf">debug</span> <span class="c1"># =&gt; 2025-08-06 18:35:59.138433 D [2082290:54184] Foo -- FOO!</span>
<span class="n">bar</span><span class="p">.</span><span class="nf">debug</span> <span class="c1"># =&gt; nothing</span>
</code></pre></div></div>

<p>While SemanticLogger only allows runtime changes of the global log level, you could likely write something yourself to change it per class.</p>

<h2 id="please-create-debuggable-systems">Please Create Debuggable Systems</h2>

<p>While you could consider everything above as a part of <em>observability</em>, to me this is distinct.  Debuggable systems don’t have to have OTel or other fancy stuff—they can write logs or write to standard output.  Debuggable systems show useful error messages that explain (or lead to an explanation of) the problem, and can be configured to produce diagnostic information that tells you what they are doing and why.</p>

<p>You can get started by creating better error messages in your tests!  Instead of writing <code class="language-plaintext highlighter-rouge">assert list.include?("value")</code>, try this:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">assert</span> <span class="n">list</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="s2">"value"</span><span class="p">),</span>
       <span class="s2">"Checking list '</span><span class="si">#{</span><span class="n">list</span><span class="si">}</span><span class="s2">' for 'value'"</span>
</code></pre></div></div>

<p>Try to make sure when any test fails, the messaging you get is everything you need to understand the problem.  Then proliferate this to the rest of your system.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Build a blog in 15ish Minutes with BrutRB]]></title>
    <link href="https://naildrivin5.com/blog/2025/07/23/build-a-blog-in-15ish-minutes-with-brutrb.html"/>
    <updated>2025-07-23T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2025/07/23/build-a-blog-in-15ish-minutes-with-brutrb.html</id>
    <content type="html"><![CDATA[<iframe title="Make a Blog App in 15(ish) minutes With BrutRB - A New Ruby Web Framework" width="560" height="315" src="https://video.hardlimit.com/videos/embed/ae7EMhwjDq9kSH5dqQ9swV" frameborder="0" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe>

<p>This is a whirlwind tour of the basics of <a href="https://brutrb.com">Brut</a>, where I build a blog from scratch in a bit over 15 minutes.  The app is fully tested and even has basic observability as a bonus.  The only software you need to install is Docker.</p>

<!-- more -->

<ul>
  <li><a href="https://youtu.be/hQSSy3AVB28">Watch on YouTube</a>, if PeerTube isn’t working for you for some reason</li>
  <li><a href="https://github.com/thirdtank/blog-demo">Check out the source code</a>, if you don’t want to watch a video</li>
</ul>

<div data-ad=""></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Brut: A New Web Framework for Ruby]]></title>
    <link href="https://naildrivin5.com/blog/2025/07/08/brut-a-new-web-framework-for-ruby.html"/>
    <updated>2025-07-08T09:00:00+00:00</updated>
    <id>https://naildrivin5.com/blog/2025/07/08/brut-a-new-web-framework-for-ruby.html</id>
    <content type="html"><![CDATA[<div style="display: flex; align-items: center; gap: 0.5rem;">
<figure style="float: left">
  <a href="/images/BrutLogoTall.png">
    <img src="/images/BrutLogoTall.png" alt="A brown rectangle with a large capital 'B'. Underneathe is 'brut'" />
  </a>
</figure>
<p class="p">
<a href="https://brutrb.com">Brut</a> aims to be a simple, yet fully-featured web framework for Ruby. It's different than other Ruby web frameworks.  Brut has no controllers, verbs, or resources. You build pages, forms, and single-action handlers. You write HTML, which is generated on the server. You can write all the JavaScript and CSS you want.
</p>
</div>
<!-- more -->

<p>Here’s a web page that tells you what time it is:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TimePage</span> <span class="o">&lt;</span> <span class="no">AppPage</span>
  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">clock</span><span class="p">:)</span>
    <span class="vi">@clock</span> <span class="o">=</span> <span class="n">clock</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">page_template</span>
    <span class="n">header</span> <span class="k">do</span>
      <span class="n">h1</span> <span class="p">{</span> <span class="s2">"Welcome to the Time Page!"</span> <span class="p">}</span>
      <span class="no">TimeTag</span><span class="p">(</span><span class="ss">timestamp: </span><span class="vi">@clock</span><span class="p">.</span><span class="nf">now</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span>

<span class="k">end</span>
</code></pre></div></div>

<p>Brut is built around low-abstraction and low-ceremony, but is not low-level like Sinatra.  It’s a web framework. Your Brut apps have builtin OpenTelemetry-based instrumentation, a <a href="https://sequel.jeremyevans.net/">Sequel</a>-powered data access layer, and developer automation based on <code class="language-plaintext highlighter-rouge">OptionParser</code>-powered command-line apps.</p>

<p><a href="https://brutrb.com">Brut</a> can be installed right now, and you can build and run an
app in minutes. You don’t even have to install Ruby.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; docker run \
        -v "$PWD":"$PWD" \
        -w "$PWD" \
        -it \
        thirdtank/mkbrut \
        mkbrut my-new-app
&gt; cd my-new-app
&gt; dx/build &amp;&amp; dx/start
&gt; dx/exec bin/setup
&gt; dx/exec bin/dev
# =&gt; localhost:6502 is waiting
</code></pre></div></div>

<p>There’s a full-fledged example app called <a href="https://github.com/thirdtank/adrs.cloud">ADRs.cloud</a> you can run right now and see how it works.</p>

<h2 id="what-you-get">What You Get</h2>

<p><a href="https://brutrb.com">Brut has extensive documentation</a>, however these are some highlights:</p>

<h3 id="bruts-core-design-is-around-classes-that-are-instantiated-into-objects-upon-which-methods-are-called">Brut’s core design is around classes that are instantiated into objects, upon which methods are called.</h3>

<ul>
  <li>No excessive <code class="language-plaintext highlighter-rouge">include</code> calls to create a massive blob of functions.</li>
  <li>No Hashes of Whatever. Your session, flash, and form parameters are all actual classes and defined data types.</li>
  <li>Minimal reliance of dynamically-defined methods or <code class="language-plaintext highlighter-rouge">method_missing</code>.  Almost every
method <a href="https://brutrb.com/api/index.html">has documentation</a>.</li>
</ul>

<h3 id="brut-leverages-the-modern-web-platform">Brut leverages the modern Web Platform.</h3>

<ul>
  <li>Client-side and server-side form validation is unified into one user experience.</li>
  <li><a href="https://brutrb.com/brut-js/api/index.html">BrutJS</a> is an ever-evolving <em>library</em>
of autonomous custom elements AKA web components to progressively enhance your
HTML.</li>
  <li>With <a href="https://esbuild.github.io/">esbuild</a>, you can write regular CSS and have
it instantly packaged, minified, and hashed. No PostCSS, No SASS.</li>
</ul>

<h3 id="brut-sets-up-good-practices-by-default">Brut sets up good practices by default.</h3>

<ul>
  <li>Your app will have a reasonable content security policy.</li>
  <li>Your database columns aren’t null by default.</li>
  <li>Your foreign keys will a) exist, b) be indexed, and c) not be nullable by
default.</li>
  <li>Time, available through Brut’s <code class="language-plaintext highlighter-rouge">Clock</code>, is always timezone-aware.</li>
  <li>Localization is there and is as easy as we can make it. We hope to make it easier.</li>
</ul>

<h3 id="brut-uses-awesome-ruby-gems">Brut uses awesome Ruby gems</h3>

<ul>
  <li>RSpec is how you write your tests. Brut includes custom matchers to make it
easier to focus on what your code should do.</li>
  <li>Faker and FactoryBot will set up your test and dev data</li>
  <li>Phlex generates your HTML. No, we won’t be supporting HAML.</li>
</ul>

<h3 id="brut-doesnt-recreate-configuration-with-yaml">Brut doesn’t recreate configuration with YAML.</h3>

<ul>
  <li>I18n uses the <a href="https://github.com/ruby-i18n/i18n">i18n gem</a>, with translations <em>in a Ruby Hash</em>. No YAML.</li>
  <li>Dynamic configuration is in the environment, managed in dev and test by <a href="https://github.com/bkeepers/dotenv">the
dotenv gem</a>. No YAML.</li>
  <li>OK, the dev environment’s <code class="language-plaintext highlighter-rouge">docker-compose.dx.yml</code> is YAML. But that’s <strong>it</strong>.</li>
  <li>YAML, not even <del>once</del>twice.</li>
</ul>

<h3 id="brut-doesnt-create-abstractions-where-none-were-needed">Brut doesn’t create abstractions where none were needed.</h3>

<ul>
  <li><em>Is this the index action of the widgets resource or the show action of the widget-list resource?</em> is a question you will never have to ask yourself or your team. The widgets page is called <code class="language-plaintext highlighter-rouge">WidgetsPage</code> and available at <code class="language-plaintext highlighter-rouge">/widgets</code>.</li>
  <li><em>My <code class="language-plaintext highlighter-rouge">Widgets</code> class accesses the <code class="language-plaintext highlighter-rouge">WIDGETS</code> table, but it also has all the domain logic of a widget!</em> No it doesn’t. <code class="language-plaintext highlighter-rouge">DB::Widget</code> gets your data.  You can make <code class="language-plaintext highlighter-rouge">Widget</code> do whatever you want. Heck, make a <code class="language-plaintext highlighter-rouge">WidgetService</code> for all we care!</li>
  <li><em>What if our HTML had controllers but they were not the same as the controllers in our back-end?</em> There aren’t any controllers. You don’t want them, you don’t have to make them.</li>
  <li><em>What about monads or algebraic data types or currying or maybe having everything
be a <code class="language-plaintext highlighter-rouge">Proc</code> because <code class="language-plaintext highlighter-rouge">call</code>?!</em> You don’t have to understand any part of that question.  But if you want your business logic to use functors, go for it. We won’t stop you.</li>
</ul>

<div data-ad=""></div>

<h2 id="why">WHY?!?!?!</h2>

<p>I know, we can vibe away all the boilerplate required for Rails apps.  But how much fun is that?  How much do you enjoy setting up RSpec, again, in your new Rails app? How tired are you of changing the “front end solution” every few years?  And aren’t you just <em>tired</em> of debating where your business logic goes or if it’s OK to use HTTP <code class="language-plaintext highlighter-rouge">DELETE</code> (tunneled over a <code class="language-plaintext highlighter-rouge">_method</code> param in a <code class="language-plaintext highlighter-rouge">POST</code>) to archive a widget?</p>

<p>I know I am.</p>

<p>I want to have fun building web apps, which means I want write Ruby, use HTML, and leverage the browser. Do you know how awesome browsers are now?  Also, Ruby 3.4 is pretty great as well. I’d like to use it.</p>

<p>What I <em>don’t</em> want is endless flexibility, constant architectural decision-making, or pointless debates about stuff that doesn’t matter.</p>

<p>I just want to have fun building web apps.</p>

<h2 id="next-steps">Next Steps</h2>

<p>I will continue working <a href="https://brutrb.com/roadmap.html">toward a 1.0 of Brut</a>, building web apps and enjoying the process.  I hope you will, too!</p>

<figure style="float: left">
  <a href="/images/BrutLogoStop.png">
    <img src="/images/BrutLogoStop.png" alt="A brown rectangle with 'BrutRB' in large letters centered.  It is in the style of the Washington, DC metro. Below BrutRB are four colored dots that each have a label. They are in the style of a metro line. The red dot has 'RB' in it and is labeled 'Ruby'. The orange dot has 'WP' in it and is labeled HTML/CSS/JS. The blue dot has 'PL' in it, and is labeled 'Phlex'. The green dot has 'RS' in it and is labeled 'Rspec'" />
  </a>
</figure>
]]></content>
  </entry>
  
</feed>
