<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>posts on olafalders.com</title><link>https://www.olafalders.com/posts/</link><description>Recent content in posts on olafalders.com</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>© 2026 Olaf Alders</copyright><lastBuildDate>Thu, 15 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.olafalders.com/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Can Others Explain My Work Without Me?</title><link>https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/</guid><description>&lt;p&gt;I’ve recently found the time to revisit &lt;a href="https://www.mymindisracing.com" target="_blank"&gt;a passion project&lt;/a&gt;. The app has a number of moving parts and I was struggling to distill it down to an elevator pitch. I&amp;rsquo;m not actually pitching anything to anyone, but it seemed like a worthwhile exercise to try to summarize this thing that I’m building in a way that someone who doesn’t care how it works would understand.&lt;/p&gt;
&lt;p&gt;I got started by using &lt;code&gt;claude&lt;/code&gt; and the &lt;code&gt;brainstorming&lt;/code&gt; skill that the &lt;a href="https://github.com/obra/superpowers" target="_blank"&gt;superpowers&lt;/a&gt; plugin provides. That allowed me to get a clear definition of the problem I was trying to solve, an honest list of what the app currently does and a succinct summary of what makes it unique. Armed with this knowledge, I was already better able to describe the problem that I was solving and the solution which I was providing. Then I stumbled across a blog post that &lt;a href="https://www.anildash.com" target="_blank"&gt;Anil Dash&lt;/a&gt; had just published. In it he describes &lt;a href="https://www.anildash.com/2025/12/05/talk-about-us-without-us/" target="_blank"&gt;a framework for thinking about the things you communicate&lt;/a&gt;, so that when you do share a message it becomes something that other people can repeat accurately and without difficulty. To quote the premise:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;They have to be able to talk about us without us. What this phrase means, in its simplest form, is that you have to tell a story so clear, so concise, so memorable and evocative that people can repeat it for you even after you&amp;rsquo;ve left the room. And the people who hear it need to be able to do this the first time they hear the story. Whether it&amp;rsquo;s the idea behind a new product, the core promise of a political campaign, or the basic takeaway from a persuasive essay (guess what the point of this one is!) — not only do you have to explain your idea and make your case, you have to be teaching your listener how to do the same thing for themselves.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;— Anil Dash, &lt;a href="https://www.anildash.com/2025/12/05/talk-about-us-without-us/" target="_blank"&gt;They have to be able to talk about us without us&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="1920"
height="1200"
src="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/featured_hu_55fc1892f83f47d6.jpeg"
srcset="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/featured_hu_55fc1892f83f47d6.jpeg 800w, https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/featured_hu_e4928dd7e5da9c59.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;&lt;a href="https://www.flickr.com/photos/28687472@N05/9434755558" target="_blank"&gt;Microphone&lt;/a&gt;&amp;rdquo; by &lt;a href="https://www.flickr.com/photos/28687472@N05" target="_blank"&gt;drestwn&lt;/a&gt; is licensed under &lt;a href="https://creativecommons.org/licenses/by/2.0/?ref=openverse" target="_blank"&gt;CC BY 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This sounded like what I was trying to do, but it&amp;rsquo;s even more ambitious in its scope. I liked it. A lot. I wanted to try it out, but since I&amp;rsquo;m lazy, I wanted to see if I could use AI to help. Using the skill creation skill that &lt;a href="https://github.com/obra/superpowers" target="_blank"&gt;superpowers&lt;/a&gt; provides, it took just a few minutes to turn a blog post into a reusable skill. I simply told Claude where to find the post and it did the rest. I tried the skill, loved the result, and I was off to the races. After test driving the skill on my passion project, I reached out to Anil to see if it was ok to (ab)use his content in this way. He graciously consented.&lt;/p&gt;
&lt;p&gt;After trying the skill out for a few other things, I realized that packaging it as a plugin would make for the most convenient way for others to try it out as well. I asked Claude to create a plugin from the skill and that also took just a few minutes. You can find the plugin at &lt;a href="https://github.com/oalders/talk-about-us" target="_blank"&gt;https://github.com/oalders/talk-about-us&lt;/a&gt;. For a quick start, if you have &lt;code&gt;claude&lt;/code&gt; installed, enter:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude plugin marketplace add oalders/talk-about-us &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude plugin install talk-about-us@talk-about-us&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;(The repository contains a &amp;ldquo;skill&amp;rdquo; which is not specific to Claude. Check the &lt;a href="https://github.com/oalders/talk-about-us" target="_blank"&gt;README&lt;/a&gt; for instructions on how to use this with other AI clients).&lt;/p&gt;
&lt;p&gt;This new skill will then be available to audit your content based on the principles in Anil&amp;rsquo;s post. Enough talk. Let&amp;rsquo;s see it in action. We are going to demonstrate this skill by seeing if one of my other passion projects, &lt;a href="https://www.prettygoodping.com" target="_blank"&gt;PrettyGoodPing&lt;/a&gt;, is doing a good job of communicating to the world what it does. I give you exhibit A. We&amp;rsquo;ll start with a screenshot of the site because (spoiler alert), the audit came back with so many issues that I&amp;rsquo;m going to need to rework the front page. I&amp;rsquo;ll show you the before:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/prettygoodping-before.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="PrettyGoodPing homepage before audit"
width="1932"
height="2234"
src="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/prettygoodping-before_hu_d4dd46a02ed8acd0.png"
srcset="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/prettygoodping-before_hu_d4dd46a02ed8acd0.png 800w, https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/prettygoodping-before_hu_b29e685b46efe049.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/prettygoodping-before.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want to try this at home, paste the following into Claude Code after you have installed the plugin:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Use the talk about us &lt;span class="nb"&gt;test&lt;/span&gt; to audit the content on the front page of https://www.prettygoodping.com/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You may see something like the following:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;&lt;strong&gt;Content Summary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The front page presents PrettyGoodPing as a monitoring dashboard for web developers, focusing on SSL/TLS certificates, domain expiry, server ping, and uptime checks. The messaging is technical and feature-focused.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;❌ 1. The Absence Test: FAIL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Can someone authentically explain this when I&amp;rsquo;m not there?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Current messaging:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Monitor SSL/TLS &amp;amp; Domain Expiry, Uptime, and Server Ping&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;PrettyGoodPing [Beta] is a configurable dashboard for web developers&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heavy jargon: &amp;ldquo;TLS/SSL,&amp;rdquo; &amp;ldquo;domain registrations,&amp;rdquo; &amp;ldquo;server uptime,&amp;rdquo; &amp;ldquo;URL availability&amp;rdquo;&lt;/li&gt;
&lt;li&gt;No one would naturally say this when recommending the service&lt;/li&gt;
&lt;li&gt;Requires technical knowledge to understand what&amp;rsquo;s being offered&lt;/li&gt;
&lt;li&gt;Very formal, not conversational&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What someone might actually say: &amp;ldquo;It&amp;rsquo;s a tool that watches your website stuff and emails you before things break&amp;rdquo; - but the current copy doesn&amp;rsquo;t enable this kind of natural explanation.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;❌ 2. The Distinctiveness Test: FAIL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Does this say what only WE can say?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Generic phrases that any competitor could use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;configurable dashboard for web developers&amp;rdquo;&lt;/li&gt;
&lt;li&gt;The feature list (SSL, domain, ping, uptime) is standard for monitoring tools&lt;/li&gt;
&lt;li&gt;&amp;ldquo;optional email alerts&amp;rdquo; - not distinctive&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What IS distinctive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The name &amp;ldquo;PrettyGoodPing&amp;rdquo; has personality (modest, unpretentious)&lt;/li&gt;
&lt;li&gt;Specific limits (25 certs, 5 domains) are concrete&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Built with Perl&amp;rdquo; goes against the grain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Problem: The distinctive personality suggested by the name isn&amp;rsquo;t carried into the copy. The messaging could belong to any monitoring service.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;❌ 3. The Emotional Resonance Test: FAIL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Is this emotionally gripping or comprehensively technical?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Current approach: Comprehensively technical&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lists features and limits without explaining why you should care&lt;/li&gt;
&lt;li&gt;No connection to the pain point (expired certs breaking your site, surprise downtime)&lt;/li&gt;
&lt;li&gt;No emotional benefit (peace of mind, sleeping soundly, avoiding embarrassment)&lt;/li&gt;
&lt;li&gt;Missing the &amp;ldquo;what problem does this solve&amp;rdquo; narrative&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Missed opportunities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The anxiety of discovering an expired SSL cert at 3am&lt;/li&gt;
&lt;li&gt;The relief of proactive monitoring&lt;/li&gt;
&lt;li&gt;The embarrassment of users seeing security warnings&lt;/li&gt;
&lt;li&gt;The simplicity of &amp;ldquo;set it and forget it&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;❌ 4. The Values-First Test: FAIL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Are values embedded in how people naturally discuss this?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The name suggests values: &amp;ldquo;Pretty Good&amp;rdquo; implies modesty, pragmatism, honesty (not overpromising &amp;ldquo;amazing&amp;rdquo; or &amp;ldquo;ultimate&amp;rdquo; monitoring)&lt;/p&gt;
&lt;p&gt;The copy suggests different values: Technical precision, comprehensiveness, feature completeness&lt;/p&gt;
&lt;p&gt;Disconnect: The personality and values aren&amp;rsquo;t woven into the language. There&amp;rsquo;s an opportunity to embrace the &amp;ldquo;pretty good&amp;rdquo; ethos - reliable, unpretentious, does what it says on the tin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Recommendations&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lead with the problem/emotion, not features&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;❌ &amp;ldquo;Monitor SSL/TLS &amp;amp; Domain Expiry, Uptime, and Server Ping&amp;rdquo;&lt;/p&gt;
&lt;p&gt;✅ &amp;ldquo;Never wake up to an expired SSL certificate again&amp;rdquo;&lt;/p&gt;
&lt;p&gt;✅ &amp;ldquo;Your sites are being watched, so you can sleep&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use natural, shareable language&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;❌ &amp;ldquo;configurable dashboard for web developers focused on monitoring TLS/SSL certificates&amp;rdquo;&lt;/p&gt;
&lt;p&gt;✅ &amp;ldquo;A pretty good way to know when your website stuff is about to expire&amp;rdquo;&lt;/p&gt;
&lt;p&gt;✅ &amp;ldquo;We&amp;rsquo;ll email you before your SSL certs, domains, or servers go sideways&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Embrace the personality from your name&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;PrettyGoodPing&amp;rdquo; name is brilliant - modest, honest, approachable. Carry that through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;We&amp;rsquo;re not fancy, but we&amp;rsquo;re reliable&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Does what it says, and does it pretty well&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Simple monitoring that actually works&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make it repeatable&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Test: Can someone explain this at a meetup without sounding robotic?&lt;/p&gt;
&lt;p&gt;❌ Current: &amp;ldquo;It&amp;rsquo;s a configurable dashboard for monitoring TLS/SSL certificates, domain registrations, server uptime, and URL availability&amp;rdquo;&lt;/p&gt;
&lt;p&gt;✅ Better: &amp;ldquo;It watches your SSL certificates and domains, and emails you before they expire. Pretty straightforward.&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Show concrete value, not just features&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of listing emoji-decorated features, try:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;25 SSL certificates watched 24/7&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Email alerts 10 days before disaster strikes&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Because finding out your cert expired from an angry user email is the worst&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Specific Rewrite Suggestions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hero Section (current):&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;&amp;ldquo;Monitor SSL/TLS &amp;amp; Domain Expiry, Uptime, and Server Ping&amp;rdquo;
&amp;ldquo;PrettyGoodPing [Beta] is a configurable dashboard for web developers&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Hero Section (suggested):&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;&amp;ldquo;Sleep better knowing your sites are being watched&amp;rdquo;
&amp;ldquo;We monitor your SSL certificates, domains, and uptime - and email you before things break. It&amp;rsquo;s pretty good at it.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Feature Section (current):&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;🔒 TLS/SSL Cert Expiry&lt;/p&gt;
&lt;p&gt;🌐 Domain Name Expiry&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Feature Section (suggested):&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;🔒 Never miss an SSL renewal - We&amp;rsquo;ll bug you 10 days before it expires&lt;/p&gt;
&lt;p&gt;🌐 Your domain won&amp;rsquo;t disappear - 3 weeks warning before expiry&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Summary Score: 0/4 Tests Passed&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The content is technically accurate but fails all four shareability tests. It&amp;rsquo;s jargon-heavy, generic, emotionally flat, and doesn&amp;rsquo;t embody the personality suggested by the excellent brand name.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; You have a distinctive, personable name (&amp;ldquo;PrettyGoodPing&amp;rdquo;) but corporate, generic copy. Let the personality shine through in the messaging.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;p&gt;I never considered my content to be corporate or generic, but here we are. I love that the skill pulls no punches. It reminds me of my first time attending my local masters swim club. The coach told me two things.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&amp;ldquo;Your stroke is killing me&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Your stroke is a recipe for shoulder pain&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When I heard this feedback, I instantly knew I was at the right place. I wanted to become a better swimmer rather than swimming in compliments. My thoughts on the skill results are similar. What I need is an honest assessment rather than cheerleading.&lt;/p&gt;
&lt;p&gt;(As an aside, the coach was correct and I have the scans of my rotator cuff to prove it.)&lt;/p&gt;
&lt;p&gt;Now that I have feedback from the skill, I can work to address it. It&amp;rsquo;s tempting to have Claude do the work and pat yourself on the back, but I think you are going to get the best results with a collaborative approach. Allow Claude to point out the problems and make some suggestions. Then take the suggestions and make them your own. In my own work with this skill, I have taken the feedback to heart but also taken pains to update my content using my own words. I can still ask Claude to evaluate the new content, but the words I use will be my own. If I&amp;rsquo;m trying to say what truly only I can say, then it kind of makes sense for me to be the one to say it. Also, I write because it brings me joy. I&amp;rsquo;m not going to ask Claude to write my blog posts in the same way that I won&amp;rsquo;t ask it to solve my daily New York Times puzzles. I use AI to do the boring stuff, so that I can enjoy the good stuff.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2026/01/15/can-others-explain-my-work-without-me/featured.jpeg"/></item><item><title>Oops! I just broke git-bisect</title><link>https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/</link><pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;There are lots of opinions on how to group changes in your git history. I think most people would agree that a commit should contain a group of changes which logically belong together. However, deciding what merits being in the same commit could be a matter of contention. I try to follow one simple rule — do not break &lt;code&gt;git-bisect&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;According to the &lt;a href="https://git-scm.com/docs/git-bisect" target="_blank"&gt;git-bisect documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;This command uses a binary search algorithm to find which commit in your project&amp;rsquo;s history introduced a bug. You use it by first telling it a &amp;ldquo;bad&amp;rdquo; commit that is known to contain the bug, and a &amp;ldquo;good&amp;rdquo; commit that is known to be before the bug was introduced. Then git bisect picks a commit between those two endpoints and asks you whether the selected commit is &amp;ldquo;good&amp;rdquo; or &amp;ldquo;bad&amp;rdquo;. It continues narrowing down the range until it finds the exact commit that introduced the change.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;If I&amp;rsquo;m honest, I rarely actually use &lt;code&gt;bisect&lt;/code&gt;, but I like this mental model because it forces me to ask myself if the code will still work as advertised when it&amp;rsquo;s checked out at this specific commit. If this is something that could get flagged by &lt;code&gt;bisect&lt;/code&gt; as broken code, then I want to re-think what I&amp;rsquo;ve included in this set of changes.&lt;/p&gt;
&lt;p&gt;It mostly comes down to &amp;ldquo;if a change breaks something, then the fix should also belong in the change&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="1962"
height="1436"
src="https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/featured_hu_d5b3f01f885750e4.jpeg"
srcset="https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/featured_hu_d5b3f01f885750e4.jpeg 800w, https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/featured_hu_86506cad1d8a4c46.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/8404611@N06/6170496511" target="_blank"&gt;Compass&lt;/a&gt; by Walt Stoneburner is licensed under &lt;a href="https://creativecommons.org/licenses/by/2.0/" target="_blank"&gt;CC BY 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Why This Matters
&lt;div id="why-this-matters" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-this-matters" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Even if you never &lt;code&gt;bisect&lt;/code&gt;, the knock-on effects of thinking about your commits in this way can be helpful both to you and others.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code reviewers can follow your code without having to keep a mental checklist of &amp;ldquo;This change breaks &amp;lsquo;A&amp;rsquo; &amp;ndash; I&amp;rsquo;ll watch the subsequent commits to see where &amp;lsquo;A&amp;rsquo; gets fixed again&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Future debugging becomes easier because you can see all relevant changes in the same set&lt;/li&gt;
&lt;li&gt;CI failures become harder to dismiss with &amp;ldquo;we were never expecting this commit to work 100%&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Your commit history becomes a provably linear set of working changes rather than an aspirational attempt at correctness&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;The Common Anti-Pattern
&lt;div id="the-common-anti-pattern" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-common-anti-pattern" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s examine a common workflow for fixing a bug:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write a test which demonstrates the bug (&lt;code&gt;commit a&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Fix the bug (&lt;code&gt;commit b&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The beauty of this approach is that you can demonstrate that feature x was broken at &lt;code&gt;commit a&lt;/code&gt;, but is clearly fixed by &lt;code&gt;commit b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The problem is that you have now introduced a point in your history where things don&amp;rsquo;t work. Anyone who lands at this point in future will need to clarify if you have truly introduced a bug and whether that bug has been fixed in a subsequent commit. This slows down code exploration, possibly making a tedious task even more unpleasant. So, while the intent of this approach is clearly one of good faith, the side effects can be problematic.&lt;/p&gt;
&lt;p&gt;For me, I might produce the two commits as part of my workflow, but I would squash them together before shipping my code for review. If you do want to have the two unique commits, I&amp;rsquo;d suggest wrapping the failing test in a &lt;code&gt;SKIP&lt;/code&gt; or &lt;code&gt;TODO&lt;/code&gt; or &lt;code&gt;FIXME&lt;/code&gt; block or whatever your testing framework supports. Remove the &lt;code&gt;skip&lt;/code&gt;/&lt;code&gt;todo&lt;/code&gt;/&lt;code&gt;fixme&lt;/code&gt; in &lt;code&gt;commit b&lt;/code&gt; as a demonstration that your bug has been fixed. That makes your intentions clear while not introducing new test failures into the commit history.&lt;/p&gt;
&lt;p&gt;One reason it&amp;rsquo;s important to regulate this yourself is that your CI may not catch the commit with the broken test. If you wait until you have &lt;code&gt;commits A and B&lt;/code&gt; ready before you ship to CI, then your CI will likely test only the last commit rather than both. You&amp;rsquo;ll be blissfully unaware that you&amp;rsquo;ve broken &lt;code&gt;git-bisect&lt;/code&gt;. Unless your code reviewer runs the tests on all of your commits, they&amp;rsquo;ll also be none the wiser.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Making Review Easier, Not Harder
&lt;div id="making-review-easier-not-harder" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#making-review-easier-not-harder" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Does this methodology make reviewing commits more difficult? I don&amp;rsquo;t think so. If all of the necessary changes are in the same commit, then there should be less cognitive load placed on the reviewer. However, this is not a rule, but rather an approach to consider. If you&amp;rsquo;re staring a monster commit in the face, do what you need to in order to untangle it. &lt;code&gt;git-bisect&lt;/code&gt; may not be the hill you want to die on.&lt;/p&gt;
&lt;p&gt;While this approach can certainly make for larger commits, hopefully you’re dealing with this via well commented code or well commented commit messages. If your code is self-documenting, then congratulations on being a mythical creature whose code requires no further explanation.&lt;/p&gt;
&lt;p&gt;Even if you don&amp;rsquo;t buy into the whole &lt;code&gt;git-bisect&lt;/code&gt; approach, this can still serve as a helpful way to think about your code. Keep the things together that are required to prevent new broken tests from being introduced.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There will be exceptions to this rule, of course. Maybe you are vendoring third-party dependencies. A massive update of node modules has broken one of your tests. Your changes are harder to spot in the thousands of lines of JSON changes. This might be one of those times where you have one commit consisting solely of &lt;code&gt;npm&lt;/code&gt; updates and a following commit (or more) to fix breaking changes. I treat the &lt;code&gt;git-bisect&lt;/code&gt; approach not as a rule, but as a guideline. Use it when it makes your code better, but not when it makes your code worse.&lt;/p&gt;
&lt;p&gt;Related posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.olafalders.com/categories/git/" &gt;More posts about git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2026/01/05/On-the-Integrity-of-git-bisect/featured.jpeg"/></item><item><title>On a Prettier PATH</title><link>https://www.olafalders.com/2025/07/16/on-a-prettier-path/</link><pubDate>Wed, 16 Jul 2025 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2025/07/16/on-a-prettier-path/</guid><description>&lt;p&gt;Knowing about your &lt;code&gt;ENV&lt;/code&gt; variables can be really helpful. Knowing how to make
them more readable can be even more helpful. Today we are going to look at some
strategies for making env variables (and &lt;code&gt;$PATH&lt;/code&gt; in particular) easier to read
and easier to reason about. We&amp;rsquo;ll also touch on how to install tools like &lt;a href="https://github.com/houseabsolute/ubi" target="_blank"&gt;ubi&lt;/a&gt;
and &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt; in GitHub CI and take a quick peek at
how using &lt;code&gt;$GITHUB_STEP_SUMMARY&lt;/code&gt; can provide a quality of life improvement for
those of us looking at CI logs.&lt;/p&gt;
&lt;p&gt;As I run through various scenarios, I will use a Docker container so that we
have a minimal environment. My real life environment is way messier. I won&amp;rsquo;t
burden you with that, so let&amp;rsquo;s see what the Docker env looks like by default:&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Default env
&lt;div id="the-default-env" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-default-env" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ env
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ada083235895
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SHLVL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOTOOLCHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/workspace
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOLANG_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.24.4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I see a couple of problems. First off, it&amp;rsquo;s hard for me to see what I want when
things aren&amp;rsquo;t sorted. Let&amp;rsquo;s sort our env.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Sorted env
&lt;div id="the-sorted-env" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-sorted-env" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ env &lt;span class="p"&gt;|&lt;/span&gt; sort
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOLANG_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.24.4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOTOOLCHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ada083235895
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/workspace
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SHLVL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This is already better, but I have to say that the &lt;code&gt;PATH&lt;/code&gt; is not easy to to read.
I don&amp;rsquo;t want to spend a lot of time staring at it. Can we do better? Well, we
can isolate the variable to start with.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Split PATH
&lt;div id="the-split-path" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-split-path" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;What if we could split on the colon? That would help.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; tr : &lt;span class="s1"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/go/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/local/go/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/local/sbin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/local/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/sbin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/sbin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/bin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/07/16/on-a-prettier-path/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="A pretty path"
width="2048"
height="1365"
src="https://www.olafalders.com/2025/07/16/on-a-prettier-path/featured_hu_93cd4ef0622e168c.jpeg"
srcset="https://www.olafalders.com/2025/07/16/on-a-prettier-path/featured_hu_93cd4ef0622e168c.jpeg 800w, https://www.olafalders.com/2025/07/16/on-a-prettier-path/featured_hu_e7f677638bd5d2a1.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/07/16/on-a-prettier-path/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A pretty path&lt;/em&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/14922165@N00/9660892548"&gt;Forrest H. Dutlinger Natural Area (Revisit) (2)&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/14922165@N00"&gt;Nicholas_T&lt;/a&gt; is licensed under &lt;a rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC BY 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;A Tabular env
&lt;div id="a-tabular-env" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#a-tabular-env" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Fortunately&lt;/strong&gt; we&amp;rsquo;re getting somewhere. &lt;strong&gt;Unfortunately&lt;/strong&gt; I&amp;rsquo;ve now run out of
patience with shell scripting. It turns out that
&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt; has us covered, though. Let&amp;rsquo;s see the &lt;code&gt;ENV&lt;/code&gt;
in a tabular format.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known summary var
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ Name ┃ Value ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ GOLANG_VERSION ┃ 1.24.4 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ GOPATH ┃ /go ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ GOTOOLCHAIN ┃ &lt;span class="nb"&gt;local&lt;/span&gt; ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ HOME ┃ /root ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ HOSTNAME ┃ ada083235895 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ PATH ┃ /go/bin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ ┃ /usr/local/go/bin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ ┃ /usr/local/sbin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ ┃ /usr/local/bin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ ┃ /usr/sbin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ ┃ /usr/bin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ ┃ /sbin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ ┃ /bin ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ PWD ┃ /workspace ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ SHLVL ┃ &lt;span class="m"&gt;1&lt;/span&gt; ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ TERM ┃ xterm ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━┛&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This feels like an improvement to me. We have a sortend &lt;code&gt;ENV&lt;/code&gt; with aligned
columns and our &lt;code&gt;$PATH&lt;/code&gt; is split on newlines, making it easier to distinguish
between similar paths like &lt;code&gt;/usr/local/bin&lt;/code&gt; and &lt;code&gt;/usr/local/sbin&lt;/code&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;A JSON env
&lt;div id="a-json-env" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#a-json-env" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Maybe we want do do something programmatic with &lt;code&gt;ENV&lt;/code&gt; or maybe the tabular
layout does not look good in our terminal logging. We do have another option:
JSON.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="err"&gt;is&lt;/span&gt; &lt;span class="err"&gt;known&lt;/span&gt; &lt;span class="err"&gt;summary&lt;/span&gt; &lt;span class="err"&gt;var&lt;/span&gt; &lt;span class="err"&gt;--json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;GOLANG_VERSION&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.24.4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;GOPATH&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;GOTOOLCHAIN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/root&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;HOSTNAME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ada083235895&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;PATH&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/go/bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/usr/local/go/bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/usr/local/sbin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/usr/local/bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/usr/sbin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/usr/bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/sbin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/bin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;PWD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/workspace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;SHLVL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;TERM&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;xterm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Cool! Now I can get the first item in my &lt;code&gt;PATH&lt;/code&gt; via &lt;code&gt;jq&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known summary var --json &lt;span class="p"&gt;|&lt;/span&gt; jq .PATH&lt;span class="o"&gt;[&lt;/span&gt;0&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;/go/bin&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;Logging env in CI via Markdown
&lt;div id="logging-env-in-ci-via-markdown" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#logging-env-in-ci-via-markdown" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;d love to emit this in my CI logs so that I can get debugging info on my
environment. Let&amp;rsquo;s install &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt; into our
container in GitHub Actions:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install is&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;oalders/install-ubi-action@v0.0.6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;oalders/is&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Display summaries&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; is known summary os --md &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; is known summary var --md &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;What happened?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We used
&lt;a href="https://github.com/oalders/install-ubi-action" target="_blank"&gt;oalders/install-ubi-action&lt;/a&gt;
to install &lt;a href="https://github.com/houseabsolute/ubi" target="_blank"&gt;ubi&lt;/a&gt;, which is the
&amp;ldquo;Universal Binary Installer&amp;rdquo;. &lt;code&gt;ubi&lt;/code&gt; allows us to install
&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt; without needing to care about which
release we need to download and install.&lt;/li&gt;
&lt;li&gt;We installed &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;We emitted a summary for the os and for the environment variables&lt;/li&gt;
&lt;li&gt;The summary was added to the &lt;code&gt;$GITHUB_STEP_SUMMARY&lt;/code&gt; environment variable&lt;/li&gt;
&lt;li&gt;We used a flag which has just recently been added to &lt;code&gt;is&lt;/code&gt;: &lt;code&gt;--md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;--md&lt;/code&gt; flag allows us to emit data in a Markdown table
format. Why would we want to do this? Well, it turns out that GitHub logs are
not always easy to read, and they can be constrained by width. So, if you have
some really long paths in your environment, tabular data becomes close to
unreadable.&lt;/p&gt;
&lt;p&gt;GitHub offers the &lt;code&gt;$GITHUB_STEP_SUMMARY&lt;/code&gt; variable as a nice solution to this.
If markdown ends up in this variable, it gets rendered in an HTML summary of
that build step. This means you get way more flexibility in how the data is
rendered. There&amp;rsquo;s also the bonus that you don&amp;rsquo;t have to scroll through giant
log files to find it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="CI"
width="2494"
height="2160"
src="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci_hu_52805765c9a8ef2f.png"
srcset="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci_hu_52805765c9a8ef2f.png 800w, https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci_hu_8a66311ce85a1e45.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And our &lt;code&gt;PATH&lt;/code&gt; looks great:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci-path.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="CI"
width="2494"
height="1190"
src="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci-path_hu_8e8daf5e03f4a115.png"
srcset="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci-path_hu_8e8daf5e03f4a115.png 800w, https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci-path_hu_58445a39b12b2d47.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/07/16/on-a-prettier-path/ci-path.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also, I can now embed the table directly into this blog post without needing to
do any manual conversion to Markdown.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is known summary var --md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GOLANG_VERSION&lt;/td&gt;
&lt;td&gt;1.24.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GOPATH&lt;/td&gt;
&lt;td&gt;/go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GOTOOLCHAIN&lt;/td&gt;
&lt;td&gt;local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HOME&lt;/td&gt;
&lt;td&gt;/root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HOSTNAME&lt;/td&gt;
&lt;td&gt;44db2d2f4c32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATH&lt;/td&gt;
&lt;td&gt;/go/bin&lt;br&gt;/usr/local/go/bin&lt;br&gt;/usr/local/sbin&lt;br&gt;/usr/local/bin&lt;br&gt;/usr/sbin&lt;br&gt;/usr/bin&lt;br&gt;/sbin&lt;br&gt;/bin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PWD&lt;/td&gt;
&lt;td&gt;/workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHLVL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TERM&lt;/td&gt;
&lt;td&gt;xterm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 class="relative group"&gt;So Long!
&lt;div id="so-long" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#so-long" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I think I&amp;rsquo;m not quite done with adding new commands to
&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt;, but for today I&amp;rsquo;m done with writing about
it. 😅&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/07/16/on-a-prettier-path/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2025/07/16/on-a-prettier-path/featured.jpeg"/></item><item><title>How Many Versions of "X" Are in my $PATH?</title><link>https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/</link><pubDate>Tue, 17 Jun 2025 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;Occasionally I run into a situation where I have more than one executable with
the same name installed in my &lt;code&gt;$PATH&lt;/code&gt;. This is to be expected, and it&amp;rsquo;s
generally not a problem until the executables are not found in the correct
order. And what is the correct order? Usually &amp;ldquo;correct&amp;rdquo; is whatever arbitrary
order makes sense for the thing that I&amp;rsquo;m currently doing, but it generally
means that I want the thing with the highest version number to be found first.&lt;/p&gt;
&lt;p&gt;For instance, the &lt;code&gt;bash&lt;/code&gt; that ships with macOS is pretty old, so I generally
install whatever &lt;code&gt;homebrew&lt;/code&gt; has available and I want to default to that in
every case.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ which -a bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/opt/homebrew/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/bin/bash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now, if I want to know which version of each is currently installed, I just
have to remember the correct incantation for &lt;code&gt;bash&lt;/code&gt;. Is it?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bash version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash -v&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash -V&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exec bash --version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash --version&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s the last one (and also the next to last, which has some cleaner output).
Probably the last would have been my first guess, but maybe not, depending on
how much of a hurry I am in.&lt;/p&gt;
&lt;p&gt;If I&amp;rsquo;m curious about which versions are actually installed, I just need to copy
each path and run &lt;code&gt;--version&lt;/code&gt; on it. That&amp;rsquo;s ok, but it&amp;rsquo;s a bit clumsy. I could
also cobble together some command line magic, but now I&amp;rsquo;ve really become
distracted from the task at hand. Since I have
&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is: an inspector for your environment&lt;/a&gt;
installed, this becomes a bit easier.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is there bash --all
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ Path ┃ Version ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ /opt/homebrew/bin/bash ┃ 5.2.37 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ /bin/bash ┃ 3.2.57 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┗━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━┛&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;What if I&amp;rsquo;d rather see it as &lt;code&gt;JSON&lt;/code&gt;?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is there bash --all --json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/opt/homebrew/bin/bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;5.2.37&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3.2.57&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now I can use &lt;code&gt;jq&lt;/code&gt; to get the version of the macOS installed executable.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is there bash --all --json &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;.[1].version&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;3.2.57&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I don&amp;rsquo;t know offhand why I would want to do that, but the important thing is
that I &lt;em&gt;can&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="800"
height="790"
src="https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/featured.jpeg"
srcset="https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/featured.jpeg 800w, https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/92855856@N00/10947544804"&gt;Numbers&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/92855856@N00"&gt;andymag&lt;/a&gt; is licensed under &lt;a rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC BY 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It gets interesting with things I may have even more versions of. Maybe
the latest version is not where I think it is:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is there perl --all
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ Path ┃ Version ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ /Users/olaf/.plenv/shims/perl ┃ v5.40.0 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ /opt/homebrew/bin/perl ┃ v5.40.2 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ /usr/bin/perl ┃ v5.34.1 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━┛&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Possibly this can help me find things that I no longer need. It looks like
&lt;a href="https://github.com/mason-org/mason.nvim" target="_blank"&gt;Mason&lt;/a&gt; has been managing my &lt;code&gt;gopls&lt;/code&gt;
install for a while. I can probably delete the first &lt;code&gt;gopls&lt;/code&gt; in my &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is there gopls --all
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ Path ┃ Version ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ /Users/olaf/go/bin/gopls ┃ 0.15.3 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ /Users/olaf/.local/share/nvim/mason/bin/gopls ┃ 0.18.1 ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━┛&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Because I work with computers, my days are full of minutia that slow me down.
Little quality of life improvements can go a long way towards removing these
tiny roadblocks. &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is: an inspector for your
environment&lt;/a&gt; may be helpful to you as well.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2025/06/17/How-Many-Versions-of-X-are-in-my-PATH/featured.jpeg"/></item><item><title>The Great PTS CLI Throwdown</title><link>https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/</link><pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/</guid><description>&lt;p&gt;I have this kind of annual tradition at the Perl Toolchain Summit where, at
some point in the weekend, I look over &lt;a href="https://github.com/pjcj" target="_blank"&gt;Paul Johnson&amp;rsquo;s&lt;/a&gt; shoulder and admire his
terminal setup. He knows his way around tmux, neovim and lots of other tools. I
make it a point to check in with him again before the weekend is over so that I
can steal everything. By the time the weekend is over we are both gone and I
haven&amp;rsquo;t had a chance to poach his setup. At some point afterwards I&amp;rsquo;ll share
some tool &lt;a href="https://fosstodon.org/@oalders" target="_blank"&gt;in the Fediverse&lt;/a&gt; that I think is cool and Paul will inevitably point me at something
newer, shinier and cooler.&lt;/p&gt;
&lt;p&gt;This year the tables were turned and Paul came to me on the first day of PTS to chat
CLI. We decided that we&amp;rsquo;d get a few of us together on the following morning and
we&amp;rsquo;d trade secrets. What follows is the notes that I took.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Full disclosure: I
don&amp;rsquo;t use AI to write the content of my blog posts, but in this case I did run
my notes through claude.ai to add some summaries and links. Claude didn&amp;rsquo;t get
everything, but it was very helpful and it sped things along. Since this is
meant to be a cheat sheet rather than a piece of creative writing, I&amp;rsquo;m ok with
using AI as a tool to help get the job done.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This year&amp;rsquo;s participants in our first (annual?) CLI throwdown were, in order of
appearance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pjcj/base" target="_blank"&gt;Paul Johnson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ssoriche/dotfiles" target="_blank"&gt;Shawn Sorichetti&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mickeyn" target="_blank"&gt;Mickey Nasriachi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/simbabque" target="_blank"&gt;Julien Fiegehenn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oalders/dot-files" target="_blank"&gt;Olaf Alders&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="900"
height="600"
src="https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/featured_hu_7af2911f3e684d3a.jpeg"
srcset="https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/featured_hu_7af2911f3e684d3a.jpeg 800w, https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;Paul&amp;rsquo;s Picks
&lt;div id="pauls-picks" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#pauls-picks" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/wezterm/wezterm" target="_blank"&gt;WezTerm&lt;/a&gt;
&lt;div id="wezterm" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#wezterm" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A GPU-accelerated cross-platform terminal emulator and multiplexer implemented
in Rust. Features include native mouse support, scrollback, ligatures, and color
emoji.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/sxyazi/yazi" target="_blank"&gt;yazi&lt;/a&gt;
&lt;div id="yazi" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#yazi" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A blazingly fast terminal file manager written in Rust, based on asynchronous
I/O. Features include full asynchronous support, built-in image viewing
capabilities, code highlighting, and git integration.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/erikw/tmux-powerline" target="_blank"&gt;tmux powerline&lt;/a&gt;
&lt;div id="tmux-powerline" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tmux-powerline" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A tmux plugin giving you a hackable status bar with beautiful powerline
segments. Implemented in bash, it provides customizable segments for displaying
information like session details, music player status, battery, and more.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://tailscale.com/" target="_blank"&gt;Tailscale&lt;/a&gt;
&lt;div id="tailscale" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tailscale" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A zero-configuration VPN built on WireGuard protocol that creates a secure
network between your devices. It simplifies secure connections by handling NAT
traversal and provides easy access control.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Neovim plugins
&lt;div id="neovim-plugins" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#neovim-plugins" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/lewis6991/gitsigns.nvim" target="_blank"&gt;git-signs&lt;/a&gt;
&lt;div id="git-signs" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-signs" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Neovim plugin that shows Git changes directly in the buffer signcolumn.
Features include per-line git blame, stage/unstage changes, and navigation
between hunks of modifications.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/gen740/SmoothCursor.nvim" target="_blank"&gt;SmoothCursor&lt;/a&gt;
&lt;div id="smoothcursor" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#smoothcursor" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Neovim plugin that adds a sub-cursor to show scroll direction, making it
easier to track cursor position when using commands like &lt;code&gt;%&lt;/code&gt; or &lt;code&gt;&amp;lt;c-f&amp;gt;&lt;/code&gt; and
&lt;code&gt;&amp;lt;c-b&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/folke/which-key.nvim" target="_blank"&gt;which-key&lt;/a&gt;
&lt;div id="which-key" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#which-key" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Neovim plugin that displays a popup showing keybindings as you type, helping
users remember complex key combinations and commands. Works across various
modes, including normal, insert, and visual.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/hrsh7th/nvim-cmp" target="_blank"&gt;nvim-cmp&lt;/a&gt;
&lt;div id="nvim-cmp" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#nvim-cmp" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A completion plugin for Neovim written in Lua. It provides a powerful and
extensible completion experience with support for LSP, snippets, and custom
sources.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/iguanacucumber/magazine.nvim" target="_blank"&gt;magazine.nvim&lt;/a&gt;
&lt;div id="magazinenvim" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#magazinenvim" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;An nvim-cmp beta fork.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/bscan/PerlNavigator" target="_blank"&gt;PerlNavigator&lt;/a&gt;
&lt;div id="perlnavigator" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#perlnavigator" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A language server for Perl, providing features like code navigation,
completion, and diagnostics.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/nvim-treesitter/nvim-treesitter" target="_blank"&gt;treesitter&lt;/a&gt;
&lt;div id="treesitter" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#treesitter" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Neovim plugin providing an interface for Tree-sitter, a parser generator tool
that provides incremental parsing capabilities, enabling better syntax
highlighting and text-objects.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/yetone/avante.nvim" target="_blank"&gt;avante&lt;/a&gt;
&lt;div id="avante" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#avante" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Neovim plugin designed to emulate the behaviour of the
&lt;a href="https://www.cursor.com/" target="_blank"&gt;Cursor&lt;/a&gt; AI IDE.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/ravitemer/mcphub.nvim" target="_blank"&gt;mcp hub&lt;/a&gt;
&lt;div id="mcp-hub" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mcp-hub" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Neovim plugin that integrates MCP (Model Context Protocol) servers into your
workflow.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/OXY2DEV/markview.nvim" target="_blank"&gt;markview&lt;/a&gt;
&lt;div id="markview" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#markview" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Neovim plugin for viewing and editing markdown files.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/petertriho/nvim-scrollbar" target="_blank"&gt;nvim-scrollbar&lt;/a&gt;
&lt;div id="nvim-scrollbar" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#nvim-scrollbar" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A customizable scrollbar for Neovim that can be integrated with other plugins to
display git changes or diagnostics in the scrollbar.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.zsh.org/" target="_blank"&gt;zsh&lt;/a&gt;
&lt;div id="zsh" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#zsh" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;An extended shell with improved completion, scripting capabilities, and plugins.
It&amp;rsquo;s highly customizable and offers numerous features beyond the standard bash.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/eza-community/eza" target="_blank"&gt;eza&lt;/a&gt;
&lt;div id="eza" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#eza" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A modern replacement for the &lt;code&gt;ls&lt;/code&gt; command with more colorful and human-friendly
output, including git integration for directories.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.raycast.com/" target="_blank"&gt;Raycast&lt;/a&gt;
&lt;div id="raycast" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#raycast" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A productivity tool for macOS that provides a quick launcher, extensible
commands, and scriptable automation capabilities.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/exelban/stats" target="_blank"&gt;stats&lt;/a&gt;
&lt;div id="stats" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#stats" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A macOS system monitor in your menu bar that shows detailed system statistics
like CPU usage, memory, network, and more.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Shawn&amp;rsquo;s Picks
&lt;div id="shawns-picks" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#shawns-picks" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/nikitabobko/aerospace" target="_blank"&gt;aerospace&lt;/a&gt;
&lt;div id="aerospace" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#aerospace" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A tiled window manager for macOS that arranges windows automatically and
provides keyboard shortcuts for window management.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://git-scm.com/docs/git-restore" target="_blank"&gt;git restore&lt;/a&gt;
&lt;div id="git-restore" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-restore" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A Git command to restore working tree files, allowing you to discard changes or
restore files from a specific commit.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.lazyvim.org/" target="_blank"&gt;lazyvim&lt;/a&gt;
&lt;div id="lazyvim" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lazyvim" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A Neovim configuration framework that provides a pre-configured and opinionated
setup with various plugins and sensible defaults.&lt;/p&gt;
&lt;h3 class="relative group"&gt;ctrl-a
&lt;div id="ctrl-a" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ctrl-a" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A Vim/Neovim feature that allows auto-incrementing numbers in text.&lt;/p&gt;
&lt;h3 class="relative group"&gt;tmux keybindings in WezTerm
&lt;div id="tmux-keybindings-in-wezterm" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tmux-keybindings-in-wezterm" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Using tmux-like key mappings within the WezTerm terminal emulator for consistent
workflow.&lt;/p&gt;
&lt;h3 class="relative group"&gt;caps-lock ctrl + escape
&lt;div id="caps-lock-ctrl--escape" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#caps-lock-ctrl--escape" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A keyboard remapping that makes Caps Lock act as Control when held and Escape
when tapped.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://karabiner-elements.pqrs.org/" target="_blank"&gt;karabiner&lt;/a&gt;
&lt;div id="karabiner" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#karabiner" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A powerful keyboard customizer for macOS that allows complex remappings and
custom modifications.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/sharkdp/bat" target="_blank"&gt;bat&lt;/a&gt;
&lt;div id="bat" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#bat" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A cat clone with syntax highlighting, git integration, and line numbers, making
it easier to view file contents.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://fishshell.com/" target="_blank"&gt;fish&lt;/a&gt;
&lt;div id="fish" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#fish" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A user-friendly command line shell with syntax highlighting, autosuggestions,
and smart tab completions.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/dandavison/delta" target="_blank"&gt;delta&lt;/a&gt;
&lt;div id="delta" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#delta" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A syntax-highlighting pager for git, diff, and grep output that improves
readability of diffs.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/samwho/spacer" target="_blank"&gt;spacer&lt;/a&gt;
&lt;div id="spacer" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#spacer" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A utility for adding visual separation or spacing in user interfaces.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://atuin.sh/" target="_blank"&gt;atuin&lt;/a&gt;
&lt;div id="atuin" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#atuin" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A tool that provides a searchable history with context for your shell, syncing
across multiple machines.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.jetpack.io/devbox/" target="_blank"&gt;devbox&lt;/a&gt;
&lt;div id="devbox" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#devbox" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A command-line tool for creating isolated, reproducible development
environments.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://direnv.net/" target="_blank"&gt;direnv&lt;/a&gt;
&lt;div id="direnv" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#direnv" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;An environment switcher that automatically loads and unloads environment
variables depending on the current directory.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://wezterm.org/quickselect.html" target="_blank"&gt;WezTerm quick select&lt;/a&gt;
&lt;div id="wezterm-quick-select" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#wezterm-quick-select" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A feature in WezTerm that allows quick selection and copying of text patterns
like URLs, paths, or IPs.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.chezmoi.io/" target="_blank"&gt;chezmoi&lt;/a&gt;
&lt;div id="chezmoi" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#chezmoi" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A dotfile manager that helps keep track and synchronize your dotfiles across
multiple machines.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://asciinema.org/" target="_blank"&gt;asciinema&lt;/a&gt;
&lt;div id="asciinema" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#asciinema" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A tool for recording and sharing terminal sessions with playback capabilities.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Mickey&amp;rsquo;s Picks
&lt;div id="mickeys-picks" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mickeys-picks" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://gnome-terminator.org/" target="_blank"&gt;terminator&lt;/a&gt;
&lt;div id="terminator" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#terminator" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A terminal emulator that allows you to arrange multiple terminals in a grid-like
structure, each with configurable options.&lt;/p&gt;
&lt;h3 class="relative group"&gt;ctrl-s stop streaming output
&lt;div id="ctrl-s-stop-streaming-output" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ctrl-s-stop-streaming-output" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A terminal shortcut that freezes the terminal output, which can be resumed with
ctrl-q (software flow control). This feature can be disabled in the shell using
&lt;code&gt;stty -ixon&lt;/code&gt;&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/tmux-plugins/tmux-resurrect" target="_blank"&gt;tmux-resurrect&lt;/a&gt;
&lt;div id="tmux-resurrect" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tmux-resurrect" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A tmux plugin that allows saving and restoring tmux sessions across system
restarts.&lt;/p&gt;
&lt;h3 class="relative group"&gt;cd is an alias to pushd
&lt;div id="cd-is-an-alias-to-pushd" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cd-is-an-alias-to-pushd" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A shell configuration where the &lt;code&gt;cd&lt;/code&gt; command is aliased to &lt;code&gt;pushd&lt;/code&gt;, maintaining
a directory stack for easy navigation. Complemented with &lt;code&gt;cd-&lt;/code&gt; aliased to
&lt;code&gt;popd&lt;/code&gt;.&lt;/p&gt;
&lt;h3 class="relative group"&gt;zmv for mass renaming via regex
&lt;div id="zmv-for-mass-renaming-via-regex" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#zmv-for-mass-renaming-via-regex" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A (zsh) tool for batch renaming files using regular expressions.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/" target="_blank"&gt;pdftk&lt;/a&gt;
&lt;div id="pdftk" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#pdftk" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A command-line tool for manipulating PDF documents, including merging,
splitting, and form filling.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/facebook/PathPicker" target="_blank"&gt;fpp&lt;/a&gt;
&lt;div id="fpp" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#fpp" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Facebook Path Picker, a tool that helps you select files from command output for
further operations.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/kepkin/dev-shell-essentials" target="_blank"&gt;highlight&lt;/a&gt;
&lt;div id="highlight" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#highlight" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A simple tool that adds colors to matched text input.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Some favorite git commands
&lt;div id="some-favorite-git-commands" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#some-favorite-git-commands" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;h4 class="relative group"&gt;git checkout -p
&lt;div id="git-checkout--p" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-checkout--p" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;A Git command that allows interactive selection of hunks to check out, similar
to &lt;code&gt;git add -p&lt;/code&gt; but for discarding changes.&lt;/p&gt;
&lt;h4 class="relative group"&gt;git rebase -i
&lt;div id="git-rebase--i" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-rebase--i" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Interactively manipulate (reword comments, reorder or squash) commits (that are
ahead of upstream).&lt;/p&gt;
&lt;h4 class="relative group"&gt;git grep -W
&lt;div id="git-grep--w" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-grep--w" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Match showing the function context (e.g. Perl sub).&lt;/p&gt;
&lt;h4 class="relative group"&gt;git push origin HEAD
&lt;div id="git-push-origin-head" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-push-origin-head" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Push current local branch to remote.&lt;/p&gt;
&lt;h4 class="relative group"&gt;git branch &amp;ndash;merged | grep -v master | xargs &amp;ndash;no-run-if-empty git branch -d
&lt;div id="git-branch-merged--grep--v-master--xargs-no-run-if-empty-git-branch--d" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-branch-merged--grep--v-master--xargs-no-run-if-empty-git-branch--d" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Remove merged branches from local directory (use an alias).&lt;/p&gt;
&lt;h4 class="relative group"&gt;git clean -dfx
&lt;div id="git-clean--dfx" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-clean--dfx" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Clean working tree from all untracked files (I have it aliased to distclean in
.gitconfig).&lt;/p&gt;
&lt;h2 class="relative group"&gt;Julien&amp;rsquo;s Picks
&lt;div id="juliens-picks" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#juliens-picks" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/jonas/tig" target="_blank"&gt;tig&lt;/a&gt;
&lt;div id="tig" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tig" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A text-mode interface for git that provides a visual interface for navigating
commits, diffs, and branches.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Editor&amp;rsquo;s note: I have previously &lt;a href="https://www.olafalders.com/2024/01/17/tig-git-browser/" &gt;written about tig&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://shutter-project.org/" target="_blank"&gt;shutter&lt;/a&gt;
&lt;div id="shutter" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#shutter" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A feature-rich screenshot tool with editing capabilities, annotations, and
various capture modes.&lt;/p&gt;
&lt;h3 class="relative group"&gt;peek
&lt;div id="peek" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#peek" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;h2 class="relative group"&gt;Olaf&amp;rsquo;s Picks
&lt;div id="olafs-picks" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#olafs-picks" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/FelixKratz/SketchyBar" target="_blank"&gt;Sketchybar&lt;/a&gt;
&lt;div id="sketchybar" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sketchybar" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A highly customizable macOS status bar replacement that can display system
information, application status, and custom scripts. I recently &lt;a href="2025/05/28/enhancing-sketchybar-with-is-battery/" &gt;wrote about SketchyBar&lt;/a&gt;.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.hammerspoon.org/" target="_blank"&gt;Hammerspoon&lt;/a&gt;
&lt;div id="hammerspoon" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#hammerspoon" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A tool for powerful automation of macOS, allowing you to write Lua scripts that
hook into system events and perform complex tasks.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://ohmyposh.dev/" target="_blank"&gt;oh-my-posh&lt;/a&gt;
&lt;div id="oh-my-posh" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#oh-my-posh" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A prompt theme engine for any shell that provides a customizable and beautiful
prompt with git status, execution time, and more.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.alfredapp.com/" target="_blank"&gt;Alfred&lt;/a&gt;
&lt;div id="alfred" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#alfred" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A productivity application for macOS that offers features like hotkeys,
keywords, text expansion, and clipboard history.&lt;/p&gt;
&lt;h4 class="relative group"&gt;clipboard
&lt;div id="clipboard" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#clipboard" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Alfred&amp;rsquo;s clipboard history feature that stores text, images, and files, making
them accessible via keyboard shortcut.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;a href="https://github.com/oalders/alfred-metacpan-workflow" target="_blank"&gt;alfred-metacpan&lt;/a&gt;
&lt;div id="alfred-metacpan" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#alfred-metacpan" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;An Alfred workflow for searching the Comprehensive Perl Archive Network (CPAN).&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://www.cloudflare.com/products/tunnel/" target="_blank"&gt;Cloudflare tunnels&lt;/a&gt;
&lt;div id="cloudflare-tunnels" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cloudflare-tunnels" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A service that creates secure connections between your local resources and the
Cloudflare network, without exposing public IPs.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/crate-ci/typos" target="_blank"&gt;typos&lt;/a&gt;
&lt;div id="typos" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#typos" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A source code spell checker that helps catch typos in your code, comments, and
documentation.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt;
&lt;div id="is" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#is" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;An inspector for your environment. I have &lt;a href="http://localhost:1313/categories/is/" target="_blank"&gt;written too much about
is&lt;/a&gt; already.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/oalders/debounce" target="_blank"&gt;debounce&lt;/a&gt;
&lt;div id="debounce" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#debounce" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Limit the rate at which a command can fire. I have written about
&lt;a href="https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/" &gt;debounce&lt;/a&gt;.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://cli.github.com/" target="_blank"&gt;gh&lt;/a&gt;
&lt;div id="gh" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#gh" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;GitHub&amp;rsquo;s official command line tool for interacting with GitHub repositories,
issues, and pull requests directly from the terminal.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://metacpan.org/dist/Open-This/view/script/ot" target="_blank"&gt;ot&lt;/a&gt;
&lt;div id="ot" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ot" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A command line utility tool with specialized functionality. I have written &lt;a href="https://www.perl.com/article/how-do-i-open-this-/" target="_blank"&gt;an
intro to Open::This&lt;/a&gt; and &lt;a href="https://www.olafalders.com/2022/01/05/open-this-file-from-inside-vim/" target="_blank"&gt;a
vim integration
guild&lt;/a&gt;.
I should write up &lt;a href="https://github.com/oalders/dot-files/blob/5128252e83558530b941b91494a6f1262599cbfb/nvim/lua/conf/open-this.lua" target="_blank"&gt;my neovim integration
sometime&lt;/a&gt;.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/houseabsolute/ubi" target="_blank"&gt;ubi&lt;/a&gt;
&lt;div id="ubi" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ubi" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A command line tool for installing universal binaries across different
platforms.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/houseabsolute/precious" target="_blank"&gt;precious&lt;/a&gt;
&lt;div id="precious" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#precious" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A linter and formatter harness.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/tummychow/git-absorb" target="_blank"&gt;git-absorb&lt;/a&gt;
&lt;div id="git-absorb" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-absorb" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A git command that automatically identifies and squashes fixup commits into
their relevant commits.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/charmbracelet/gum" target="_blank"&gt;gum&lt;/a&gt;
&lt;div id="gum" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#gum" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A tool for glamorous shell scripts, helping create interactive CLI components
like inputs, prompts, and spinners.&lt;/p&gt;
&lt;h3 class="relative group"&gt;&lt;a href="https://github.com/bensadeh/tailspin" target="_blank"&gt;tailspin&lt;/a&gt;
&lt;div id="tailspin" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tailspin" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;A log file highlighter that improves readability of log files in the terminal
with color coding and formatting.&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a rel="noopener noreferrer" href="https://commons.wikimedia.org/w/index.php?curid=63950904"&gt;File:Roppongi 3K 3K (Professional wrestling moves).jpg&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://commons.wikimedia.org/w/index.php?title=User:%E3%82%BE%E3%83%BC%E3%83%92%E3%83%A7%E3%83%BC&amp;action=edit&amp;redlink=1"&gt;ゾーヒョー&lt;/a&gt; is licensed under &lt;a rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-sa/4.0/?ref=openverse"&gt;CC BY-SA 4.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2025/06/03/the-great-pts-cli-throwdown/featured.jpeg"/></item><item><title>Battery Power-Ups: Enhancing SketchyBar with "is"</title><link>https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/</link><pubDate>Wed, 28 May 2025 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;h2 class="relative group"&gt;is: the new subcommands
&lt;div id="is-the-new-subcommands" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#is-the-new-subcommands" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;In my unrelenting quest to make &lt;del&gt;useless&lt;/del&gt; barely useful software I recently
added three new battery-related subcommands to &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is: an inspector for your
environment&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;is battery&lt;/li&gt;
&lt;li&gt;is known battery&lt;/li&gt;
&lt;li&gt;is known summary battery&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These subcommands are a thin wrapper around
&lt;a href="https://github.com/distatus/battery" target="_blank"&gt;distatus/battery&lt;/a&gt;, but they follow the
patterns of the other &lt;code&gt;is&lt;/code&gt; subcommands.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is battery --help
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Usage: is battery &amp;lt;attribute&amp;gt; &amp;lt;op&amp;gt; &amp;lt;val&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Check battery attributes. e.g. &lt;span class="s2"&gt;&amp;#34;is battery state eq charging&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Arguments:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;attribute&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;charge-rate&lt;span class="p"&gt;|&lt;/span&gt;count&lt;span class="p"&gt;|&lt;/span&gt;current-capacity&lt;span class="p"&gt;|&lt;/span&gt;current-charge&lt;span class="p"&gt;|&lt;/span&gt;design-capacity&lt;span class="p"&gt;|&lt;/span&gt;design-voltage&lt;span class="p"&gt;|&lt;/span&gt;last-full-capacity&lt;span class="p"&gt;|&lt;/span&gt;state&lt;span class="p"&gt;|&lt;/span&gt;voltage&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;op&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;eq&lt;span class="p"&gt;|&lt;/span&gt;ne&lt;span class="p"&gt;|&lt;/span&gt;gt&lt;span class="p"&gt;|&lt;/span&gt;gte&lt;span class="p"&gt;|&lt;/span&gt;in&lt;span class="p"&gt;|&lt;/span&gt;like&lt;span class="p"&gt;|&lt;/span&gt;lt&lt;span class="p"&gt;|&lt;/span&gt;lte&lt;span class="p"&gt;|&lt;/span&gt;unlike&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;val&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;They allow you to do things like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known battery state
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;discharging&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is battery count gt &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;we have 🔋&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;we have 🔋&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known summary battery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ Attribute ┃ Value ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ battery-number ┃ &lt;span class="m"&gt;1&lt;/span&gt; ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ charge-rate ┃ &lt;span class="m"&gt;0&lt;/span&gt; mW ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ count ┃ &lt;span class="m"&gt;1&lt;/span&gt; ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ current-capacity ┃ 76750.899 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ current-charge ┃ &lt;span class="m"&gt;99&lt;/span&gt; % ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ design-capacity ┃ 76498.56 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ design-voltage ┃ 13.281 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ last-full-capacity ┃ 77534.478 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ state ┃ discharging ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ voltage ┃ 13.281 V ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┗━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┛&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is known summary battery --json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;state&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;discharging&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;battery-number&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;count&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;charge-rate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;current-capacity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;76750.899&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;current-charge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;design-capacity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;76498.56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;design-voltage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;13.281&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;last-full-capacity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;77534.478&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;voltage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;13.281&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now that we have some new tools at our disposal, what can we do with them?&lt;/p&gt;
&lt;h2 class="relative group"&gt;SketchyBar (Before)
&lt;div id="sketchybar-before" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sketchybar-before" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve recently replaced the default macOS menu bar with
&lt;a href="https://github.com/FelixKratz/SketchyBar" target="_blank"&gt;SketchyBar&lt;/a&gt;. One segment of the
menu bar displays the current status of my battery (if one is installed). Let&amp;rsquo;s
have a look at my current menu bar.&lt;/p&gt;
&lt;h3 class="relative group"&gt;My Desktop
&lt;div id="my-desktop" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#my-desktop" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;As expected, my desktop doesn&amp;rsquo;t display a battery status.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/desktop.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="desktop"
width="1096"
height="394"
src="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/desktop_hu_e0c879c8dfb96081.png"
srcset="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/desktop_hu_e0c879c8dfb96081.png 800w, https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/desktop.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/desktop.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h3 class="relative group"&gt;My Laptop
&lt;div id="my-laptop" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#my-laptop" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;My laptop, which (fortunately) has a battery, does display a battery status.
Here we see the menu with the power supply plugged in and with the power supply
removed.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop-charging.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="laptop charging"
width="1330"
height="400"
src="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop-charging_hu_c442e7e34a0de592.png"
srcset="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop-charging_hu_c442e7e34a0de592.png 800w, https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop-charging_hu_8275904f9ebf5a73.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop-charging.png"&gt;&lt;/figure&gt;
&lt;/a&gt;
&lt;a href="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="laptop"
width="1330"
height="400"
src="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop_hu_b9f9e4622ee53de3.png"
srcset="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop_hu_b9f9e4622ee53de3.png 800w, https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop_hu_79ed6f786a9b6edb.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/laptop.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I don&amp;rsquo;t love the default macOS menu bar. &lt;code&gt;SketchyBar&lt;/code&gt; gives me portable way to
have the same configuration on the various Macs that I use from day to day. It
is configured via a collection of scripts and it ships with some nice default
plugins. It even runs on 2017 Macs, which is what I&amp;rsquo;m slinging for my personal
needs. Let&amp;rsquo;s dive right in and see how this helps with getting a battery icon
into the status bar.&lt;/p&gt;
&lt;p&gt;I started with this &lt;a href="https://github.com/oalders/dot-files/blob/ffb063dc527d1354fe7880a0ee7b9dc2a3e16cf3/sketchybar/sketchybarrc" target="_blank"&gt;SketchyBar
config&lt;/a&gt;.
It allows me to configure which segments I want to appear in my menu bar and
since this is basically a &lt;code&gt;bash&lt;/code&gt; script, I can dynamically configure the menu
bar based on my environment.&lt;/p&gt;
&lt;p&gt;I want to add the battery to the menu bar conditionally, since in 50% of the
cases, I won&amp;rsquo;t be on a laptop. I didn&amp;rsquo;t have a great way to see if I was using
a laptop, so initially I would manually &lt;code&gt;touch $PLUGIN_DIR/.laptop&lt;/code&gt; on the
machines where I wanted to display a battery status. That worked fine as a
stopgap.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PLUGIN_DIR&lt;/span&gt;&lt;span class="s2"&gt;/.laptop&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sketchybar --add item battery right &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set battery &lt;span class="nv"&gt;update_freq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;120&lt;/span&gt; &lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PLUGIN_DIR&lt;/span&gt;&lt;span class="s2"&gt;/battery.sh&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;button&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --subscribe battery system_woke power_source_change
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;SketchyBar&lt;/code&gt; menu items are not only configured via scripts, they can also be
updated via scripts. I started out using &lt;a href="https://github.com/oalders/dot-files/blob/ffb063dc527d1354fe7880a0ee7b9dc2a3e16cf3/sketchybar/plugins/battery.sh" target="_blank"&gt;this
script&lt;/a&gt;
to find the current available charge in the battery as well as the charging
state (is it charging right now?) The code to get those values was as follows:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PERCENTAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pmset -g batt &lt;span class="p"&gt;|&lt;/span&gt; grep -Eo &lt;span class="s2"&gt;&amp;#34;\d+%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; cut -d% -f1&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CHARGING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pmset -g batt &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;AC Power&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If I&amp;rsquo;m honest, I didn&amp;rsquo;t write that code and it doesn&amp;rsquo;t really excite me. It has
the potential to be brittle. Also, don&amp;rsquo;t ask me to explain it.&lt;/p&gt;
&lt;h2 class="relative group"&gt;SketchyBar (After)
&lt;div id="sketchybar-after" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sketchybar-after" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;After installing the latest version of &lt;code&gt;is&lt;/code&gt;, I&amp;rsquo;m able to make this a little
nicer by no longer relying on a special file to exist. Here&amp;rsquo;s a diff:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-if [ -f &amp;#34;$PLUGIN_DIR/.laptop&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+if is battery count gt 0; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That leaves us with:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; is battery count gt 0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sketchybar --add item battery right &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set battery &lt;span class="nv"&gt;update_freq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;120&lt;/span&gt; &lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PLUGIN_DIR&lt;/span&gt;&lt;span class="s2"&gt;/battery.sh&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;button&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --subscribe battery system_woke power_source_change
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;For the battery script itself, I can clean that up a little with this
diff:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-PERCENTAGE=&amp;#34;$(pmset -g batt | grep -Eo &amp;#34;\d+%&amp;#34; | cut -d% -f1)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-CHARGING=&amp;#34;$(pmset -g batt | grep &amp;#39;AC Power&amp;#39;)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+PERCENTAGE=&amp;#34;$(is known battery current-charge --round)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-if [[ &amp;#34;$CHARGING&amp;#34; != &amp;#34;&amp;#34; ]]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+if is battery state eq charging; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The current charge that I get back via
&lt;a href="https://github.com/distatus/battery" target="_blank"&gt;distatus/battery&lt;/a&gt; is sometimes a few
percentage points off from what &lt;code&gt;pmset&lt;/code&gt; reports back. I don&amp;rsquo;t know if macOS
performs some kind of smoothing, but for my purposes I&amp;rsquo;m not too concerned. I
mostly care if the battery is fully charged, almost empty or has a reasonable
amount of time left on it. Everything else is not that important.&lt;/p&gt;
&lt;p&gt;One other consideration is that &lt;code&gt;SketchyBar&lt;/code&gt; is installed via &lt;code&gt;homebrew&lt;/code&gt;
and launched via &lt;code&gt;brew services&lt;/code&gt;, which doesn&amp;rsquo;t have the same &lt;code&gt;$PATH&lt;/code&gt; as my
user environment. I ended up adding the path to my &lt;code&gt;is&lt;/code&gt; installation in both
of the &lt;code&gt;SketchyBar&lt;/code&gt; files that I need to maintain for this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/local/bin:&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;Summary
&lt;div id="summary" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#summary" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;That&amp;rsquo;s basically it. My &lt;code&gt;SketchyBar&lt;/code&gt; config is in &lt;a href="https://github.com/oalders/dot-files/blob/main/sketchybar/sketchybarrc" target="_blank"&gt;my dot-files
repo&lt;/a&gt;.
The new &lt;code&gt;is&lt;/code&gt; subcommands are well documented via &lt;code&gt;--help&lt;/code&gt; and &lt;a href="https://github.com/oalders/is/blob/main/README.md" target="_blank"&gt;the
README&lt;/a&gt;. If you can&amp;rsquo;t
remember all of the available battery attributes (and why would you) the new
&lt;code&gt;is known summary battery&lt;/code&gt; subcommand can be very handy.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t currently have a battery, it&amp;rsquo;s pretty succinct:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known summary battery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┏━━━━━━━━━━━┳━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ Attribute ┃ Value ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━╋━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ count ┃ &lt;span class="m"&gt;0&lt;/span&gt; ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┗━━━━━━━━━━━┻━━━━━━━┛&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Otherwise it can be quite verbose.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known summary battery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ Attribute ┃ Value ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┣━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━┫
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ battery-number ┃ &lt;span class="m"&gt;1&lt;/span&gt; ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ charge-rate ┃ &lt;span class="m"&gt;0&lt;/span&gt; mW ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ count ┃ &lt;span class="m"&gt;1&lt;/span&gt; ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ current-capacity ┃ 76750.899 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ current-charge ┃ &lt;span class="m"&gt;99&lt;/span&gt; % ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ design-capacity ┃ 76498.56 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ design-voltage ┃ 13.281 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ last-full-capacity ┃ 77534.478 mWh ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ state ┃ discharging ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┃ voltage ┃ 13.281 V ┃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┗━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┛&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It&amp;rsquo;s also available as JSON.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is known summary battery --json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;state&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;discharging&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;battery-number&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;count&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;charge-rate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;current-capacity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;76750.899&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;current-charge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;design-capacity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;76498.56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;design-voltage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;13.281&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;last-full-capacity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;77534.478&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;voltage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;13.281&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/27095118@N00/6771097341"&gt;Lightning&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/27095118@N00"&gt;nathan_lounds&lt;/a&gt; is marked with &lt;a rel="noopener noreferrer" href="https://creativecommons.org/publicdomain/mark/1.0/?ref=openverse"&gt;Public Domain Mark 1.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/pd.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2025/05/28/enhancing-sketchybar-with-is-battery/featured.jpeg"/></item><item><title>Debounce All of the Things</title><link>https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/</link><pubDate>Tue, 25 Feb 2025 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="3072"
height="2304"
src="https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/featured_hu_bb0a99b52f3a130.jpeg"
srcset="https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/featured_hu_bb0a99b52f3a130.jpeg 800w, https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/featured_hu_f305e83393765c3c.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/39427725@N00/4324706280"&gt;my pocket watch rules&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/39427725@N00"&gt;Cubosh&lt;/a&gt; is licensed under &lt;a rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC BY 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;" /&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The TL;DR
&lt;div id="the-tldr" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-tldr" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I created a tool to limit how often commands are run. It&amp;rsquo;s called
&lt;a href="https://github.com/oalders/debounce" target="_blank"&gt;debounce&lt;/a&gt; and I like it a lot. You can
skip right to &lt;a href="https://github.com/oalders/debounce" target="_blank"&gt;the repository&lt;/a&gt; to start
debouncing, but today, I really want to tell a story.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Caveat
&lt;div id="the-caveat" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-caveat" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Before I lay my soul bare, let me address in advance the kind of comment I
anticipate receiving:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;You can already do some or &lt;strong&gt;all of this&lt;/strong&gt; with [some other tool]&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;I did this for the same reason I might solve a crossword puzzle. We don&amp;rsquo;t
collectively give up on the puzzle because someone else has already solved it.
The joy is in discovering the solution. For me, it&amp;rsquo;s a nice diversion. Some days
when work has been a real slog and I haven&amp;rsquo;t gotten a lot of wins, working on
something simple like this gives me an easy win and I just feel better about my
life. Maybe this is my therapy. Please don&amp;rsquo;t take my therapy away from me, even
if it&amp;rsquo;s just re-inventing wheels.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Setup
&lt;div id="the-setup" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-setup" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Back in 2023, I wrote about
&lt;a href="https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/" &gt;is&lt;/a&gt;, which is essentially an
experiment in making it easier to write shell scripts for different
environments. In the intervening months I&amp;rsquo;ve used it basically every day and I&amp;rsquo;m
quite happy with it. Somewhere along the way, I thought I could use
&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt; to prevent me from doing things like
installing a nightly &lt;code&gt;neovim&lt;/code&gt; build more than once per day.&lt;/p&gt;
&lt;p&gt;The scenario is basically that in my
&lt;a href="https://github.com/oalders/dot-files" target="_blank"&gt;dot-files&lt;/a&gt; I run installer scripts to
take care of mundane tasks across the machines that I use. Whenever I want
something new to happen across my environments I edit an installer script and
run it to ensure that things work as expected. Often the first pass is broken in
some way. I edit the script and run it again. I make it a point to do this in
the script so that if I get distracted and move on to something else, my work is
saved and I can commit it to my repo later. If I quickly whip something up on
the command line, I often forget to add it to my dot-files.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Problem
&lt;div id="the-problem" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-problem" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The problem arises when a task in an installer script takes too long. For
instance, I may have a build step that installs the nightly &lt;code&gt;neovim&lt;/code&gt; build. If
the script fails after this point and requires multiple runs to fix, I have to
wait for the nightly build install to happen over and over. That wastes time and
network resources. I can do better.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Solution: Attempt the First
&lt;div id="solution-attempt-the-first" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-attempt-the-first" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;To address this, I added support for &lt;code&gt;is cli age&lt;/code&gt;, which allows me to do
something like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli age nvim gt &lt;span class="m"&gt;1&lt;/span&gt; day &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./installer/nvim.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;is cli age&lt;/code&gt; will search my &lt;code&gt;$PATH&lt;/code&gt; for an exact match on the provided name and
then check its age. This is great, because I now have some granularity around
when &lt;code&gt;neovim&lt;/code&gt; might be installed. I can express it in days, minutes, or even
seconds.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli age &lt;span class="o"&gt;[&lt;/span&gt;filename&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;gt&lt;span class="p"&gt;|&lt;/span&gt;lt&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;integer&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;hours&lt;span class="p"&gt;|&lt;/span&gt;minutes&lt;span class="p"&gt;|&lt;/span&gt;seconds&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s it. If you prefer a shorthand, the following aliases are available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hours | hour | h&lt;/li&gt;
&lt;li&gt;minutes | minute | m&lt;/li&gt;
&lt;li&gt;seconds | second | s&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This allows us to express the same intent with fewer characters.&lt;/p&gt;
&lt;p&gt;As an overall solution this is nice, but it breaks down really quickly for some
other cases. Imagine I want to check for updates to other software which doesn&amp;rsquo;t
have a nightly build. This particular example runs
&lt;a href="https://github.com/houseabsolute/ubi" target="_blank"&gt;ubi&lt;/a&gt; to take care of installing/upgrading
&lt;code&gt;typos&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli age typos gt &lt;span class="m"&gt;1&lt;/span&gt; day &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ubi --project crate-ci/typos --in ~/local/bin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here I&amp;rsquo;m not installing a nightly build, but rather a regular release. So if I
run my installer script 10 times and there hasn&amp;rsquo;t been a new release in the last
day or so, it will try to upgrade the binary 10 times. Sub-optimal.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Taking a Detour with Find
&lt;div id="taking-a-detour-with-find" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#taking-a-detour-with-find" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re thinking &amp;ldquo;you can already do this with &lt;code&gt;find&lt;/code&gt;&amp;rdquo;, you&amp;rsquo;re not wrong.
Let&amp;rsquo;s look at this option.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find &lt;span class="k"&gt;$(&lt;/span&gt;which nvim&lt;span class="k"&gt;)&lt;/span&gt; -mtime +1 -exec ./installer/nvim.sh &lt;span class="se"&gt;\;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It&amp;rsquo;s helpful to know that &lt;code&gt;-mtime +1&lt;/code&gt; means the file needs to be more than 1 day
old. If you want it to be younger than 1 day, you would use &lt;code&gt;-mtime -1&lt;/code&gt;. If we
wanted to limit to 10 minutes, rather than a day:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find &lt;span class="k"&gt;$(&lt;/span&gt;which nvim&lt;span class="k"&gt;)&lt;/span&gt; -mmin +10 -exec ./installer/nvim.sh &lt;span class="se"&gt;\;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So, to recap, &lt;code&gt;-mtime&lt;/code&gt; is modification days and &lt;code&gt;-mmin&lt;/code&gt; is modification minutes.
If we wanted to limit to 30 seconds, then I don&amp;rsquo;t believe &lt;code&gt;find&lt;/code&gt; has a
convenient way to do this. It&amp;rsquo;s not terrible, but I just don&amp;rsquo;t love it. Also,
don&amp;rsquo;t forget to add the &lt;code&gt;\;&lt;/code&gt; at the end of the line, or this just doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;To me using &lt;code&gt;find&lt;/code&gt; is just not intuitive. I want a syntax that&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;feels natural&lt;/li&gt;
&lt;li&gt;reads like English&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t constantly have to look up&lt;/li&gt;
&lt;li&gt;works &lt;em&gt;exactly&lt;/em&gt; the way I want it to&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;Solution: Attempt the Second
&lt;div id="solution-attempt-the-second" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-attempt-the-second" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As established above, knowing the age of an installed binary is helpful when it
comes to a nightly build, but when it comes to binaries which are released less
regularly, it&amp;rsquo;s not great. I figured I could cobble something really primitive
together, where I would &lt;code&gt;touch&lt;/code&gt; a file at a known location after a successful
installation attempt and skip subsequent installation attempts if a file exists
at the known location and is younger than a specified time.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;is cli age&lt;/code&gt; is meant to be used for things that exist in your &lt;code&gt;$PATH&lt;/code&gt;, so I
added an &lt;code&gt;is fso age&lt;/code&gt; subcommand, where &lt;code&gt;fso&lt;/code&gt; stands for &amp;ldquo;filesystem object&amp;rdquo; (a
link, a directory, a file, etc). This means we can now do something like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is fso age ~/.bash_profile gt &lt;span class="m"&gt;24&lt;/span&gt; hours &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;this file is not new&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This gives us an easy way to ensure that the last modified time of a file is
younger or older than a certain age. We will leverage this below, in order to
create a simple Bash debouncer.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Creating a debounce Function in Bash
&lt;div id="creating-a-debounce-function-in-bash" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#creating-a-debounce-function-in-bash" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;What does &lt;strong&gt;debouncing&lt;/strong&gt; mean? ChatGPT explains it as:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;Think of debouncing like pressing a button that triggers an action. If you
press the button multiple times in quick succession, it might cause the action
to be triggered repeatedly—sometimes unnecessarily. But, if the button is
debounced, it only triggers the action once, no matter how many times you
press it, as long as it’s within a set time window.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;For our purposes, we can think of debouncing as a way of telling the Operating
System to:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;run this command, but only if it hasn&amp;rsquo;t successfully been run within the last
X seconds/hours/days&amp;quot;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Consider the function below:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;db&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; -lt &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;🤬 Not enough arguments provided. Usage: debounce 6 h something&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# exit as early as possible if we can&amp;#39;t create the cache dir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# test -d appears to be slightly faster (3ms?) than mkdir -p&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;cache_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.cache/debounce
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; -d &lt;span class="nv"&gt;$cache_dir&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; mkdir -p &lt;span class="nv"&gt;$cache_dir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;units&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# everything remaining is runnable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# file is $target with slashes converted to dashes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; tr / -&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;debounce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$cache_dir&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$debounce&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; is fso age &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$debounce&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; lt &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$number&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$units&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;🚥 will not run &lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="s2"&gt; more than once every &lt;/span&gt;&lt;span class="nv"&gt;$number&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$units&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; touch &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$debounce&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can now run our function like &lt;code&gt;db 12 hours npm install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To break it down, &lt;code&gt;db&lt;/code&gt; does the following things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;requires 3 (or more) arguments, in the form of &lt;code&gt;db 12 hours npm install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;creates a cache directory, if none exists&lt;/li&gt;
&lt;li&gt;transforms the given command (in this case &amp;ldquo;npm install&amp;rdquo;) into a cache file
name&lt;/li&gt;
&lt;li&gt;checks if the cache file exists and it&amp;rsquo;s younger than the provided age check
(12 hours here), if this is true it
&lt;ul&gt;
&lt;li&gt;prints a message to assure the user that no code will be run&lt;/li&gt;
&lt;li&gt;returns&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;runs the command in all other cases&lt;/li&gt;
&lt;li&gt;if the command succeeds (exit code 0), it will &lt;code&gt;touch&lt;/code&gt; the cache file, so that
we have a timestamp which subsequent &lt;code&gt;db&lt;/code&gt; runs can use as a reference time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is simple and it works. It doesn&amp;rsquo;t require much, other than having
&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt; available to do the file age comparison. For
simple commands it is quite straightforward. You can even look at the cache
directory to see the names of commands which have already been run, which means
that a simple&lt;code&gt;ls&lt;/code&gt; will tell you when they were last successfully run to
completion. It&amp;rsquo;s not bad.&lt;/p&gt;
&lt;p&gt;However, this breaks down once you get longer and/or more complex commands.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do I need to start worrying if the length of the generated file names will
exceed OS limits?&lt;/li&gt;
&lt;li&gt;What about &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; and other special shell characters?
&lt;ul&gt;
&lt;li&gt;How will I encode them in the file name?&lt;/li&gt;
&lt;li&gt;Will it still be readable? Is there some kind of security risk to this
approach?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What if I want to run the same command in different directories, but each
needs their own cache file?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;Solution: Attempt the Third
&lt;div id="solution-attempt-the-third" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-attempt-the-third" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I could work around the limitations of the previous function, but it would
require writing more Bash code. I don&amp;rsquo;t mind a bit of Bash to get me where I
need to be, but at some point I&amp;rsquo;m more comfortable doing this in a different
language. I chose to re-implement this in Go. I could have done this in a number
of other languages, but I chose Go because &lt;a href="https://goreleaser.com/" target="_blank"&gt;goreleaser&lt;/a&gt;
makes it easy for me to distribute a binary that I can install via
&lt;a href="https://github.com/houseabsolute/ubi" target="_blank"&gt;ubi&lt;/a&gt;. My other solutions already depended
on &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt;, so there was always going to be some
kind of dependency.&lt;/p&gt;
&lt;p&gt;The new binary is called &lt;a href="https://github.com/oalders/debounce" target="_blank"&gt;debounce&lt;/a&gt;. Let&amp;rsquo;s
try it out:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ debounce &lt;span class="m"&gt;1&lt;/span&gt; day installer/nvim.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# installer runs...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ debounce &lt;span class="m"&gt;1&lt;/span&gt; day installer/nvim.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🚥 will not run &lt;span class="s2"&gt;&amp;#34;installer/nvim.sh&amp;#34;&lt;/span&gt; more than once every &lt;span class="m"&gt;1&lt;/span&gt; d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;How much delay does &lt;code&gt;debounce&lt;/code&gt; introduce? On an M3 MacBook Air we get:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;time&lt;/span&gt; debounce &lt;span class="m"&gt;1&lt;/span&gt; day installer/nvim.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🚥 will not run &lt;span class="s2"&gt;&amp;#34;installer/nvim.sh&amp;#34;&lt;/span&gt; more than once every &lt;span class="m"&gt;1&lt;/span&gt; d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;real 0m0.005s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;user 0m0.002s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sys 0m0.002s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;🚀 Not much delay at all. What if I want to check when the next run is
scheduled, without executing the command?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;debounce --status &lt;span class="m"&gt;1&lt;/span&gt; day installer/nvim.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;📁 cache location: /Users/olaf/.cache/debounce/bdb61146922d63f599045bde35c65d0606e002f88bc3099554c644ff153e11df
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🚧 cache last modified: Mon, &lt;span class="m"&gt;24&lt;/span&gt; Feb &lt;span class="m"&gt;2025&lt;/span&gt; 21:37:13 EST
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;⏲️ debounce interval: 24:00:00
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🕰️ cache age: 00:02:56
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;⏳ &lt;span class="nb"&gt;time&lt;/span&gt; remaining: 23:57:03&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s the available &lt;code&gt;debounce&lt;/code&gt; metadata. We can always wipe out the cache file
to start fresh.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rm /Users/olaf/.cache/debounce/bdb61146922d63f599045bde35c65d0606e002f88bc3099554c644ff153e11df
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ debounce --status &lt;span class="m"&gt;1&lt;/span&gt; day installer/nvim.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cache file does not exist. &lt;span class="s2"&gt;&amp;#34;installer/nvim.sh&amp;#34;&lt;/span&gt; will run on next debounce&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;The Tips
&lt;div id="the-tips" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-tips" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;Running Multi-Step Commands
&lt;div id="running-multi-step-commands" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#running-multi-step-commands" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;bash -c&lt;/code&gt; if your debounce command is really a series of chained commands.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;debounce &lt;span class="m"&gt;2&lt;/span&gt; s bash -c &lt;span class="s1"&gt;&amp;#39;sleep 2 &amp;amp;&amp;amp; date&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 class="relative group"&gt;Careful with Your Quotes
&lt;div id="careful-with-your-quotes" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#careful-with-your-quotes" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;If you need your variables to expand only at the time when your command is
running, use single quotes.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;debounce &lt;span class="m"&gt;10&lt;/span&gt; s zsh -c &lt;span class="s1"&gt;&amp;#39;foo=$(date) &amp;amp;&amp;amp; echo $foo&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;The debounce in Your CI/CD Pipeline
&lt;div id="the-debounce-in-your-cicd-pipeline" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-debounce-in-your-cicd-pipeline" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re using &lt;code&gt;debounce&lt;/code&gt; in CI/CD, you&amp;rsquo;ll want to ensure that your cache
directories are preserved between runs. You may find the &lt;code&gt;--cache-dir&lt;/code&gt; flag
helpful here.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The debounce in Your Ansible
&lt;div id="the-debounce-in-your-ansible" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-debounce-in-your-ansible" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Maybe you want to run database backups, but no more than once per day.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Perform debounced database backup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;debounce 1 day ./back-up-database.sh&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I can think of other use cases, like making sure you&amp;rsquo;re not pushing to (or
pulling from) Docker hub too often. How about using a cron to retry a query
against a flaky web service?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-crontab" data-lang="crontab"&gt;*/5 * * * * /path/to/debounce 30 m /path/to/fetch-exchange-rates.pl&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Under this scenario, &lt;code&gt;fetch-exchange-rates.pl&lt;/code&gt; will be run every 5 minutes until
it successfully fetches the latest exchange rates. Then it won&amp;rsquo;t try again for
another 30 minutes. At that point it will continue to run every 5 minutes until
it once again achieves success. And so on.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Conclusion
&lt;div id="the-conclusion" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-conclusion" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;There are lots of possibilities here. Think about where you re-run logic
unnecessarily. Maybe &lt;code&gt;debounce&lt;/code&gt; could help?&lt;/p&gt;
&lt;p&gt;For any more questions, I refer you the
&lt;a href="https://github.com/oalders/debounce?tab=readme-ov-file#introduction" target="_blank"&gt;debounce repository&lt;/a&gt;.
There is no way to comment on this blog, so you&amp;rsquo;ll have to go to GitHub to tell
me how much you hate this idea. 😅&lt;/p&gt;
&lt;p&gt;Until next time!&lt;/p&gt;
&lt;p&gt;Related posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/" &gt;is: an inspector for your environment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2025/02/25/debounce-all-of-the-things/featured.jpeg"/></item><item><title>How I Spent My First London Perl Workshop</title><link>https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/</link><pubDate>Fri, 08 Nov 2024 00:00:00 -0500</pubDate><guid>https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="splash"
width="1280"
height="960"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/featured_hu_35f56c3684d7defe.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/featured_hu_35f56c3684d7defe.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;
&lt;h2 class="relative group"&gt;Look Who&amp;rsquo;s Back
&lt;div id="look-whos-back" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#look-whos-back" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Earlier this year I was excited (one might even say well chuffed) when I heard
that the London Perl Workshop was coming back after a hiatus of several years. I
knew that in all likelihood I would not be able to attend, but it has been a
bucket list item of mine for quite some time. You may be thinking to yourself
that I have a weird bucket list. You&amp;rsquo;re not wrong.&lt;/p&gt;
&lt;p&gt;I made no plans to attend, since I would be using my professional development
days to attend
&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/" &gt;The Perl Toolchain Summit&lt;/a&gt;.
I thought I might make a short trip with my remaining budget to a conference in
NYC or Chicago, since those destinations are easy to get to from Toronto.&lt;/p&gt;
&lt;p&gt;Fast forward to October. The US conferences which had I found were fairly
expensive. I wouldn&amp;rsquo;t be able to fit a conference pass, airfare and hotel with
my remaining (or even entire) budget. I optimistically crunched some numbers
around attending the London Perl Workshop.&lt;/p&gt;
&lt;p&gt;To my surprise, I could make the travel and hotel costs work within my budget as
long as I didn&amp;rsquo;t stay any longer than from Friday to Monday. Since the workshop
is free, I didn&amp;rsquo;t have to worry about splashing out USD 600 for a day at a
conference. I casually floated to the MetaCPAN crowd that I was thinking of a
long weekend in London. &lt;a href="https://metacpan.org/author/LLAP" target="_blank"&gt;Leo&lt;/a&gt; decided to come
down as well so that we could hack on MetaCPAN in the time around the workshop.
&lt;a href="https://metacpan.org/author/MICKEY" target="_blank"&gt;Mickey&lt;/a&gt; had already planned to come.
&lt;a href="https://metacpan.org/author/ETJ" target="_blank"&gt;Mohawk&lt;/a&gt;, who hangs around in our IRC channel,
would be there as well. So I already knew I&amp;rsquo;d be in good company.&lt;/p&gt;
&lt;p&gt;My manager signed off on the trip and my family decided I wasn&amp;rsquo;t so important
that they couldn&amp;rsquo;t do without me for a few days. I was off to London. I would
not be missed.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Wheels Down London
&lt;div id="wheels-down-london" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#wheels-down-london" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Small Square Cafe"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/small-square-cafe_hu_6d4ceacd33fb7a03.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/small-square-cafe_hu_6d4ceacd33fb7a03.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/small-square-cafe.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/small-square-cafe.jpeg"&gt;&lt;figcaption&gt;Small Square Cafe&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;I flew out late on Thursday night, arriving early Friday morning. I dropped my
bags at the hotel, planning to check in after lunch. I found a really nice
little coffee shop and then walked over to Denmark Street. That took about an
hour. Mickey had said there were some cool guitar shops there. I wasn&amp;rsquo;t too
excited about looking at gear I couldn&amp;rsquo;t afford, but it did sound interesting.
It&amp;rsquo;s a very short street and I saw Mickey and
&lt;a href="https://metacpan.org/author/XSAWYERX" target="_blank"&gt;Sawyer&lt;/a&gt; on the sidewalk within a few
minutes of arriving there.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://notomguitars.com/" target="_blank"&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="No.Tom Guitars"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/no.tom_hu_809b8e6ba1d59848.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/no.tom_hu_809b8e6ba1d59848.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/no.tom.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/no.tom.jpeg"&gt;&lt;figcaption&gt;No.Tom Guitars&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mickey took me into a couple of guitar shops. In the first shop that we visited
&lt;a href="https://en.wikipedia.org/wiki/Richard_Ayoade" target="_blank"&gt;Richard Ayoade&lt;/a&gt; popped in to look
at a guitar. We all played it cool and didn&amp;rsquo;t bother him, but bumping into
someone from &lt;a href="https://en.wikipedia.org/wiki/The_IT_Crowd" target="_blank"&gt;The IT Crowd&lt;/a&gt; so
shortly after my arrival did bode well. Actually, based on the length of my
trip, &lt;a href="https://en.wikipedia.org/wiki/Travel_Man" target="_blank"&gt;Travel Man&lt;/a&gt; was also a good
fit.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="National Guitar"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/national-guitar_hu_228bbc930deeead5.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/national-guitar_hu_228bbc930deeead5.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/national-guitar.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/national-guitar.jpeg"&gt;&lt;figcaption&gt;1931 National Style 4 Guitar: 12,000 quid&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Gibson Harp Guitar"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gibson-harp_hu_52c8ceb3cece7111.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gibson-harp_hu_52c8ceb3cece7111.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gibson-harp.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gibson-harp.jpeg"&gt;&lt;figcaption&gt;1914 Gibson Harp Guitar: also 12,000 quid&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Next we ran into Leo on the street and we collectively decided to head for
lunch. The result was most excellent. At this point I was well gruntled as the
locals may or probably may not say.&lt;/p&gt;
&lt;p&gt;We did a bit of walking and then stopped for cake and coffee. Then we did a
little more walking and some shoppinng. Eventually we headed down to the
pre-workshop social. We immediately saw some familiar faces. It was great to
reconnect with people.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Honey Cake"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/honey-cake_hu_5cb56f9c51ef22b3.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/honey-cake_hu_5cb56f9c51ef22b3.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/honey-cake.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/honey-cake.jpeg"&gt;&lt;figcaption&gt;Honey Cake&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Pre-Workshop Social
&lt;div id="the-pre-workshop-social" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-pre-workshop-social" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I had been feeling somewhat proud of myself for flying in to London just for the
weekend &amp;ndash; and all the way from Canada. Surely that was impressive or something?
All of that became largely moot when
&lt;a href="https://metacpan.org/author/ISHIGAKI" target="_blank"&gt;Kenichi Ishigaki&lt;/a&gt; walked into the venue,
having arrived from Japan this same day. I hung my head in shame, knowing I had
been bested. He also was returning home on Monday like I was. Very impressive
stuff on his part. My trip was mere child&amp;rsquo;s play in comparison.&lt;/p&gt;
&lt;p&gt;At some point the venue got incredibly loud. I felt like I was losing my voice.
Leo, Sawyer, Mickey and I set out to find some dinner. That was also more than
sufficient. Eventually we got back to the hotel. It was maybe 11 pm. In all of
the excitement I didn&amp;rsquo;t get a chance to take a nap or even change out of my
overnight airplane clothes. I was ready for a solid night of sleep.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Workshop
&lt;div id="the-workshop" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-workshop" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;On Saturday morning, Leo and I headed down to the conference on foot, since our
hotel was quite close to the venu. We grabbed a coffee on the way and then did
the registration, which consisted of putting your name on a name tag and getting
your conference scarf. Providing a scarf as swag means the organizers don&amp;rsquo;t have
to take sizes or different styles into account. Very smart.&lt;/p&gt;
&lt;p&gt;Here I had the chance to (re)connect with even more people, since just a small
fraction of the attendees had been at the Friday evening social. The event was
incredibly well attended. The vibe was positive, with lots of laughs and lots of
interesting talks.&lt;/p&gt;
&lt;p&gt;I attended &lt;a href="https://metacpan.org/author/DAVECROSS" target="_blank"&gt;Dave Cross&lt;/a&gt;&amp;rsquo; talk first and
we got to have a quick chat. I had missed connecting with him in Toronto in
2023, so this was the first time we had met in person. I have been pestering him
online for a while now, so it was nice to meet face to face. I also made it a
point to reserve &lt;a href="https://metacpan.org/author/MANWAR" target="_blank"&gt;Mohammad&lt;/a&gt; for lunch before
he got scooped up by someone else, since I really didn&amp;rsquo;t have a good chance to
speak with him in Toronto either. That turned out to be a good plan, as he was
unable to attend the evening social. He also gave me a lovely gift to welcome me
to London. What an incredibly kind person. I should be giving him gifts to thank
him for all he has done to amplify my blog posts in the Perl Weekly.&lt;/p&gt;
&lt;p&gt;Lunch was Mohammad, &lt;a href="https://metacpan.org/author/PTC" target="_blank"&gt;Paul Cochrane&lt;/a&gt;, Mickey,
Sawyer and I. Sawyer gave a technical presentation on my laptop as we waited for
the food to arrive. I learned a bunch. It was nice to have Paul along since I
have also been pestering him online for a while. He doesn&amp;rsquo;t seem to mind. 🤞&lt;/p&gt;
&lt;p&gt;I had a chance to meet Stuart MacIntosh (president of The Perl and Raku
Foundation) in person. I have been working with Stuart and TPRF on sponsorship
and fundraising for the last 6 months. After many video calls we could finally
chat in person. I also was able to corner
&lt;a href="https://metacpan.org/author/MARKF" target="_blank"&gt;Mark Fowler&lt;/a&gt;, who worked on a team with me
for many years but sadly now works at CircleCI. That&amp;rsquo;s sad for me (not for him
or CircleCI). Mark is the creator of
&lt;a href="https://perladvent.org/" target="_blank"&gt;The Perl Advent Calendar&lt;/a&gt;. This is the 25th year of
the calendar. I doubt he had that kind of longevity in mind when he was inspired
over drinks to get this project out to the world.&lt;/p&gt;
&lt;p&gt;The afternoon talks were also interesting. I won&amp;rsquo;t go through an inventory.
Likewise, I won&amp;rsquo;t name every single person I met &amp;ndash; but I probably could.&lt;/p&gt;
&lt;p&gt;I should add that I found &lt;a href="https://metacpan.org/author/VANHOESEL" target="_blank"&gt;Theo&lt;/a&gt;&amp;rsquo;s
lightning talk tribute to &lt;a href="https://metacpan.org/author/ABELTJE" target="_blank"&gt;Abe&lt;/a&gt;, whom we
lost just a short while ago, to be quite moving.&lt;/p&gt;
&lt;p&gt;After the event many of us went out for social drinks, which was nice.
&lt;a href="https://metacpan.org/author/SIMBABQUE" target="_blank"&gt;Julien Fiegehenn&lt;/a&gt; sat with us at the
pub. I guess I am still name dropping. 🤔&lt;/p&gt;
&lt;p&gt;Leo, Mickey, Sawyer and I went out to find some dinner. As we were about to
order our food, it became clear to me that I was starting to reach my limits, so
I left them to order their curries without me and headed back to the hotel to
sleep. I was trying to be thoughtful by leaving before food was ordered, lest
something go to waste. Apparently in my absence the 3 of them ate a meal meant
for at least 4 people. Kudos to them and their hardy (hearty?) appetites.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Thoughts on the Conference
&lt;div id="thoughts-on-the-conference" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#thoughts-on-the-conference" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Before I get into stories about hacking, I&amp;rsquo;d like to share some thoughts on the
conference itself.&lt;/p&gt;
&lt;p&gt;It was really well organized. Thanks to &lt;a href="https://metacpan.org/author/LEEJO" target="_blank"&gt;Lee&lt;/a&gt;
and Julien and everyone who helped organize and volunteer on the day.&lt;/p&gt;
&lt;p&gt;There was a great mix of people. There were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Older people&lt;/li&gt;
&lt;li&gt;Younger people&lt;/li&gt;
&lt;li&gt;Working people&lt;/li&gt;
&lt;li&gt;Retired people&lt;/li&gt;
&lt;li&gt;A large number of attendees who had never been to this workshop before&lt;/li&gt;
&lt;li&gt;A number of attendees who had &lt;em&gt;never been to a Perl workshop before&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As I looked around the room, I saw people who were there to reconnect with old
friends, people who were looking to make new friends and a collective of quirky,
off-beat, nice people who came together out of a shared enthusiasm for arcane
knowledge and niche programming languages. It&amp;rsquo;s a place where everyone can feel
welcome and where you can give a talk about scratching some weird itch while
your peers listen silently to your every word.&lt;/p&gt;
&lt;p&gt;I hope this conference happens again soon. It was a real pleasure to attend.&lt;/p&gt;
&lt;h2 class="relative group"&gt;A Miniature meta::hack
&lt;div id="a-miniature-metahack" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#a-miniature-metahack" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Sunday morning began with finding coffee and then hacking in the hotel lobby
with Leo, Mickey and Sawyer. Before I headed to London, Julien had asked me if I
had a location for hacking. I said &amp;ldquo;maybe in one of our rooms?&amp;rdquo; He reminded me
that this is London and I was just a naive child. He was right, of course. I
could barely walk around my bed. If I flipped down my tiny table, I couldn&amp;rsquo;t
even do that. Luckily the lobby cleared out after breakfast. After that we had
access to long tables, lots of electrical sockets, water, coffee and snacks.&lt;/p&gt;
&lt;p&gt;I began the day by working on our AWS-hosted Elasticsearch instance. Elastic
kindly sponsored us with a cloud instance earlier this year when I reached out
to them, but we hadn&amp;rsquo;t been able to fully take advantage of it. It set out to
work on getting the cloud instance basically up to date with production data and
working with Mickey on his Docker setup, which has been affected by a kind of
&amp;ldquo;move fast and break things&amp;rdquo; by people who are not named Mickey. That all worked
out well.&lt;/p&gt;
&lt;p&gt;Later in the morning, Julien stopped in to hack with us. We all stepped out for
lunch and then Mickey and Sawyer headed off to the airport. Julien took me and
Leo on a sightseeing tour and then we headed back to the hotel for more hacking.
After lots of screen time, we found a really nice Chinese restaurant. After
eating, Julien headed home and Leo and I got back to hacking. I probably could
have gone to bed, but I had left something in a broken state and it was going to
eat away at me if I didn&amp;rsquo;t deal with it before bedtime. That worked out well and
I was able to sleep like a baby.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Gherkin Building"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gherkin_hu_b140e3ef93857c25.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gherkin_hu_b140e3ef93857c25.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gherkin.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/gherkin.jpeg"&gt;&lt;figcaption&gt;Gherkin Building&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Olaf at Bridge"
width="1280"
height="960"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/olaf-at-bridge_hu_a296beab4bd44d94.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/olaf-at-bridge_hu_a296beab4bd44d94.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/olaf-at-bridge.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/olaf-at-bridge.jpeg"&gt;&lt;figcaption&gt;Olaf at Bridge&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Hack Day, The Last
&lt;div id="hack-day-the-last" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#hack-day-the-last" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I got up at 6 AM on Monday to go for a run. I started off moving towards the
external setting for &amp;ldquo;Slough House&amp;rdquo; as it appears in my beloved
&lt;a href="https://en.wikipedia.org/wiki/Slow_Horses" target="_blank"&gt;Slow Horses&lt;/a&gt;. I then looked for more
interesting places to explore. I love seeing a city on foot this way. I can
cover a lot of ground and I&amp;rsquo;m also getting my workout. An added bonus is that I
have a Strava map of the run as a kind of souvenir after the trip. I have fond
memories of running in Oslo, Paris and Lyon at the various Toolchain Summits. If
I&amp;rsquo;m going to be doing that much sitting, I owe it to myself to spend some time
where I am definitely not sitting.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Slough House"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/slough-house_hu_596e9242894c02b0.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/slough-house_hu_596e9242894c02b0.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/slough-house.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/slough-house.jpeg"&gt;&lt;figcaption&gt;Slough House&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Arch"
width="1280"
height="960"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/arch_hu_cc3826decaa664f.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/arch_hu_cc3826decaa664f.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/arch.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/arch.jpeg"&gt;&lt;figcaption&gt;Arch&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Narrow Building"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/narrow-building_hu_b2e79a0b91a6fcc4.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/narrow-building_hu_b2e79a0b91a6fcc4.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/narrow-building.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/narrow-building.jpeg"&gt;&lt;figcaption&gt;Narrow Building&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Wall Garden"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/wall-garden_hu_a1e899ed5cdaffab.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/wall-garden_hu_a1e899ed5cdaffab.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/wall-garden.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/wall-garden.jpeg"&gt;&lt;figcaption&gt;Wall Garden&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Phone Booth Library"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/phone-booth-library_hu_ccfe9a24e32d46d5.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/phone-booth-library_hu_ccfe9a24e32d46d5.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/phone-booth-library.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/phone-booth-library.jpeg"&gt;&lt;figcaption&gt;Phone Booth Library&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Upon my return, Leo treated me to a bacon and egg sandwich with ketchup. I&amp;rsquo;m not
going to lie &amp;ndash; it was delicious. As we ate in this tiny shop we were able to
watch the regulars come by and order &amp;ldquo;the usual&amp;rdquo;. I wish I had a place like this
close to me. My lack of a cozy sandwich shop near my own home is likely better
for my cholesterol, but YOLO.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Bacon Egg Sandwich"
width="960"
height="1280"
src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/bacon-egg-sandwich_hu_be7675c3384650f.jpeg"
srcset="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/bacon-egg-sandwich_hu_be7675c3384650f.jpeg 800w, https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/bacon-egg-sandwich.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/bacon-egg-sandwich.jpeg"&gt;&lt;figcaption&gt;A delicious bacon and egg sandwich&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;After breakfast we did some more, you guessed it, hacking. Over sandwiches Leo
got me to realize that my plan to test a staging environment with the cloud
Elasticsearch was fine, but what was I hoping to learn? I explained to him
exactly what I was hoping to learn and it&amp;rsquo;s fair to say, I was not going to
learn much. So, I deployed the cloud Elasticsearch to the production. We could
toggle back to the bare metal Elasticsearch if this turned out to be a mistake.&lt;/p&gt;
&lt;p&gt;To my surprise, the cutover was so painless that I&amp;rsquo;m not sure anyone noticed.
The hosted Elasticsearch had data that was at least a day behind production. I
re-ran the indexer on all releases in the previous 48 hours. Now we were
basically caught up. (There did turn out to be an issue with a couple of indices
which are not related to search &amp;ndash; &lt;a href="https://metacpan.org/author/HAARG" target="_blank"&gt;Graham&lt;/a&gt;
sorted these out on the following day).&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have any benchmarks to prove it, but the site now feels faster to me. I
had been worried about the lag between the data centres when going to a remote
Elasticsearch, but it&amp;rsquo;s quite possible that the powerful system running the new
Elasticsearch is more than making up for any increased latency. For the time
being we have front end containers running in Frankfurt. They talk to back end
bare metal servers in the UK. The bare metal back ends speak to the hosted
Elasticsearch in Frankfurt. Eventually everything should reside in Frankfurt,
but that&amp;rsquo;s a conversation for another day.&lt;/p&gt;
&lt;p&gt;Aside from no longer needing to worry about keeping 3 Elasticsearch nodes up and
running on bare metal, we are now in a position to upgrade from Elasticsearch
2.4 to 5. Then we&amp;rsquo;ll move to 6 and so on. Being able to migrate from one cloud
instance to another will be far easier than trying to allocate enough hardware
to run two concurrent versions of Elasticsearch during an upgrade. I would never
point to the cloud as a solution for everything, but in this case the benefits
have become very clear to me. I would like to thank Elastic for their commitment
to The Perl Community. They have been incredibly helpful.&lt;/p&gt;
&lt;p&gt;This Elasticsearch cutover was the conclusion of work which began 6 months ago
at the Perl Toolchain Summit in Lisbon. Emotionally it feels really good. The
bulk of the work happened in bursts, generally when some of us were in the same
room or just around the time. I think remote work is amazing and I would
strongly advocate for 100% remote work, but there is still real value in face to
face meetings. I&amp;rsquo;m glad we were able to make this happen.&lt;/p&gt;
&lt;p&gt;After going live to production and not seeing anything go up (down?) in flames,
Leo and I headed to Paddington Station. He took his train home and I filled any
remaining gaps in my luggage with junk food for the kids. I&amp;rsquo;m not sure if that
makes me a good or a terrible Dad, but a precedent has been set by past trips
and at this point I&amp;rsquo;m not sure I&amp;rsquo;m in a position to break with it.&lt;/p&gt;
&lt;p&gt;As I sit in my Air Canada flight, somewhere over Newfoundland, I now look
forward to seeing some smiling faces at home and hopefully being attacked by a
completely out-of-her-mind 75 pound dog. And I&amp;rsquo;m already thinking about how I
can once again make it across the Atlantic to get some work done.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2024/11/08/how-i-spent-my-first-london-perl-workshop/featured.jpeg"/></item><item><title>I'm Still Lazy</title><link>https://www.olafalders.com/2024/08/28/i-am-still-lazy/</link><pubDate>Wed, 28 Aug 2024 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2024/08/28/i-am-still-lazy/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/08/28/i-am-still-lazy/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="1024"
height="1024"
src="https://www.olafalders.com/2024/08/28/i-am-still-lazy/featured_hu_b7678f6fade1d8bd.jpeg"
srcset="https://www.olafalders.com/2024/08/28/i-am-still-lazy/featured_hu_b7678f6fade1d8bd.jpeg 800w, https://www.olafalders.com/2024/08/28/i-am-still-lazy/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/08/28/i-am-still-lazy/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;
&lt;h2 class="relative group"&gt;This toy was somewhat broken
&lt;div id="this-toy-was-somewhat-broken" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#this-toy-was-somewhat-broken" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;About six years ago, &lt;a href="https://www.olafalders.com/2018/08/21/how-lazy-am-i/" &gt;I wrote about lazy&lt;/a&gt;, which is
a tool that will auto-install Perl dependencies for you as they are required.
It&amp;rsquo;s not unique &amp;ndash; it has been done before. But this implementation uses a
different installer (&lt;a href="https://metacpan.org/pod/App::cpm" target="_blank"&gt;cpm&lt;/a&gt;) and it also
allows you to pass in some flags directly to the installer. I think it&amp;rsquo;s fun,
but &lt;a href="https://github.com/oalders/lazy/issues/1" target="_blank"&gt;it had some problems&lt;/a&gt; and it
didn&amp;rsquo;t always work the way I wanted it to. I should have fixed it sooner, but
I&amp;rsquo;m lazy.&lt;/p&gt;
&lt;p&gt;Today I wanted to use it and I was so annoyed that I decided to have another
look at it. But, it was after my work day and I was feeling lazy. So I asked
&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/" &gt;GitHub Copilot&lt;/a&gt; to fix the
biggest bug for me. It was deep recursion. Surprisingly, Copilot immediately
grasped and fixed the issue. (At least it seems that way to me). Once that was
done I took a closer look and decided to fix some design decisions that were
making the internals overly complicated. I did that on my own. Then I added some
GitHub Actions to give me a little more confidence in my changes and soon it was
ready to ship.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Now it&amp;rsquo;s a better toy
&lt;div id="now-its-a-better-toy" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#now-its-a-better-toy" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As of right now version 1.000000 of &lt;code&gt;lazy&lt;/code&gt; is now available for you to download.
&lt;a href="https://metacpan.org/release/OALDERS/lazy-1.000000/view/lib/lazy.pm" target="_blank"&gt;You can check it out on MetaCPAN&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Have some fun with it
&lt;div id="have-some-fun-with-it" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#have-some-fun-with-it" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;At the command line
&lt;div id="at-the-command-line" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#at-the-command-line" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-perl" data-lang="perl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Auto-install missing modules globally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;perl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Mlazy&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Auto-install missing modules into ./local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;perl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Mlazy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;-Llocal&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Auto-install missing modules into ./some-other-dir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;perl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Mlazy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;-Lsome-other-dir&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 class="relative group"&gt;In your code
&lt;div id="in-your-code" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#in-your-code" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-perl" data-lang="perl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Auto-install missing modules globally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Auto-install missing modules into ./local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;local::lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;lazy&lt;/span&gt; &lt;span class="sx"&gt;qw( -L local )&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Auto-install missing modules into ./some-other-dir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;local::lib&lt;/span&gt; &lt;span class="sx"&gt;qw( some-other-dir )&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;lazy&lt;/span&gt; &lt;span class="sx"&gt;qw( -L some-other-dir )&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Auto-install missing modules into ./some-other-dir and pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# more options to App::cpm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;local::lib&lt;/span&gt; &lt;span class="sx"&gt;qw( some-other-dir )&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;lazy&lt;/span&gt; &lt;span class="sx"&gt;qw( -L some-other-dir --man-pages --verbose --no-color )&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 class="relative group"&gt;In a one-liner?
&lt;div id="in-a-one-liner" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#in-a-one-liner" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Install &lt;a href="https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/" &gt;App::perlimports&lt;/a&gt; via a
one-liner, but why would you want to?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ perl -Mlazy -MApp::perlimports -E &lt;span class="s1"&gt;&amp;#39;say &amp;#34;🧅&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🌍 global install &lt;span class="k"&gt;if&lt;/span&gt; required
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install Net-SSLeay-1.94
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install IO-Socket-SSL-2.088
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install LWP-Protocol-https-6.14
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;3&lt;/span&gt; distributions installed.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skipping autoloader file auto::Net::SSLeay::DTLS1_VERSION.al at /Users/olaf/.plenv/versions/5.40.0/lib/perl5/site_perl/5.40.0/lazy.pm line 90.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skipping autoloader file auto::Net::SSLeay::DTLS1_VERSI.al at /Users/olaf/.plenv/versions/5.40.0/lib/perl5/site_perl/5.40.0/lazy.pm line 90.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skipping autoloader file auto::Net::SSLeay::DTLS1_2_VERSION.al at /Users/olaf/.plenv/versions/5.40.0/lib/perl5/site_perl/5.40.0/lazy.pm line 90.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skipping autoloader file auto::Net::SSLeay::DTLS1_2_VER.al at /Users/olaf/.plenv/versions/5.40.0/lib/perl5/site_perl/5.40.0/lazy.pm line 90.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skipping autoloader file auto::Net::SSLeay::DTLS1_BAD_VER.al at /Users/olaf/.plenv/versions/5.40.0/lib/perl5/site_perl/5.40.0/lazy.pm line 90.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skipping autoloader file auto::Net::SSLeay::DTLS1_BAD_V.al at /Users/olaf/.plenv/versions/5.40.0/lib/perl5/site_perl/5.40.0/lazy.pm line 90.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install Mozilla-CA-20240730
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;1&lt;/span&gt; distribution installed.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install Regexp-IPv6-0.03
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;1&lt;/span&gt; distribution installed.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install Net::SSLeay is up to date. &lt;span class="o"&gt;(&lt;/span&gt;1.94&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;0&lt;/span&gt; distribution installed.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install IO::Socket::SSL is up to date. &lt;span class="o"&gt;(&lt;/span&gt;2.088&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;0&lt;/span&gt; distribution installed.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DONE install App-perlimports-0.000055
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;1&lt;/span&gt; distribution installed.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🧅&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And that&amp;rsquo;s really it. I hope you have fun using it. I hope it makes you more
productive. But, most importantly, I hope it leaves you more time in the day to
goof off and, you know, just be lazy.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/08/28/i-am-still-lazy/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2024/08/28/i-am-still-lazy/featured.jpeg"/></item><item><title>How I Spent My Perl Toolchain Summit v2024 (Lisbon)</title><link>https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/</link><pubDate>Fri, 21 Jun 2024 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/</guid><description>
&lt;h2 class="relative group"&gt;PTS 2024 in Lisbon
&lt;div id="pts-2024-in-lisbon" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#pts-2024-in-lisbon" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The Perl Toolchain Summit (PTS) is an annual event, held in Europe, where work
on improving the Perl toolchain takes place. I was fortunate to be able to
attend PTS once again this year. This year we gathered in Lisbon, Portugal from
Thursday, April 25 to Sunday, April 28. This was my 1st trip to Lisbon and my
9th PTS in total.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-at-night.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Lisbon at night"
width="600"
height="450"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-at-night-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-at-night-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-at-night-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-at-night-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;Getting There
&lt;div id="getting-there" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#getting-there" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/05/01/how-i-spent-my-perl-toolchain-summit-v2023/" &gt;Last year&lt;/a&gt;, I took my
little travel companion along for the ride. That didn&amp;rsquo;t work out for us this
year, so I flew solo, but I missed the company.&lt;/p&gt;
&lt;p&gt;On the Monday evening I flew out from YYZ. Early Tuesday morning I had breakfast
in Paris at CDG and then continued on to Lisbon.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/canele.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Canelé in Paris"
width="572"
height="600"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/canele-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/canele-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/canele-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/canele-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Canelé in Paris. The stop at CDG was worth it]&lt;/p&gt;
&lt;h2 class="relative group"&gt;Getting Settled
&lt;div id="getting-settled" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#getting-settled" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-from-the-air.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Beaches of Lisbon"
width="450"
height="600"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-from-the-air-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-from-the-air-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-from-the-air-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/lisbon-from-the-air-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[On the descent into Lisbon, the view of the sandy beaches was incredible.]&lt;/p&gt;
&lt;p&gt;My initial plan had been to spend Monday or Tuesday at the beach at Cascais,
since I had never swum in the ocean before, but &lt;a href="https://metacpan.org/author/INGY" target="_blank"&gt;INGY&lt;/a&gt; had thrown out the
idea of renting some road bikes in Lisbon and doing some cycling. The choices
were a 40km route with some tough climbs and an 80 km route with less climbing.
I had never gone on a ride of more than 65 km, but after some discussion we
decided on the 80 km route as that seemed like the easier option.&lt;/p&gt;
&lt;p&gt;After checking in at the hotel, Ingy and I picked up our rental bikes and rode
them back to the hotel. It was my first time on cobblestones with a road bike
and I experienced almost immediately how much fun that is. After I picked myself
up off the ground I was decidedly less enthusiastic about riding, but the rest
of the way back to the hotel was not so eventful.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Fado
&lt;div id="fado" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#fado" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/fado.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Fado"
width="467"
height="600"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/fado-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/fado-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/fado-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/fado-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the evening, Ingy and &lt;a href="https://metacpan.org/author/TINITA" target="_blank"&gt;Tina&lt;/a&gt; and I went out to dinner with some local
friends of Ingy who had kindly booked us all a table at
&lt;a href="https://casadelinhares.com/" target="_blank"&gt;a local restaurant&lt;/a&gt; which serves wonderful food
with performances of &lt;a href="https://en.wikipedia.org/wiki/Fado" target="_blank"&gt;Fado&lt;/a&gt; in between. The
musicians were extremely skilled and the audience was very appreciative. This
was complemented by the quality of the food and wine. It really was a night to
remember. The only thing we didn&amp;rsquo;t remember was to get to bed early. I got back
to my room at 1:30 AM and I needed to be on my bike 6 hours later. I hadn&amp;rsquo;t
taken a proper nap or thought too much about jetlag as there hadn&amp;rsquo;t really been
time.&lt;/p&gt;
&lt;h2 class="relative group"&gt;To Ride or not to Ride
&lt;div id="to-ride-or-not-to-ride" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#to-ride-or-not-to-ride" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;After getting back late, I decided I probably shouldn&amp;rsquo;t ride the next day. My
leg was still a bit sore from my fall, I had a headache from riding on the
cobblestones and (I&amp;rsquo;m not going to lie) I had had a fair amount to drink. I
debated how I should cancel on Ingy. Should I send him a text now and let him
know that I had made the safe, responsible, adult choice or should I casually
mention at 7:00 AM breakfast that I wasn&amp;rsquo;t feeling up to it? I decided to let
him know at breakfast.&lt;/p&gt;
&lt;p&gt;By the time I was having my 7:00 AM breakfast, my headache was gone, my leg was
healing nicely and I wasn&amp;rsquo;t so tired. Also, given how much I enjoyed the ride
from the bike shop to the hotel, I wasn&amp;rsquo;t looking forward to returning the bike
to the shop by myself. I figured I&amp;rsquo;d ride along and see how it all played out.
How bad could it be? If at any point I wanted to bail on the ride, I could find
a train and head back, so I headed out with a contingency plan.&lt;/p&gt;
&lt;p&gt;After maybe one minute of riding it became obvious to me that I didn&amp;rsquo;t have the
speed or the bike handling skills to keep up with Ingy and his local friend (who
had taken us out for dinner just a few hours ago and was now very kindly taking
us on one of his local routes). There was no way I was riding between two rows
of stopped cars on the cobblestones, even with 30 mm tires. However, it was a
&amp;ldquo;no drop&amp;rdquo; ride and they kindly waited for me at points along the way to ensure
we stayed together. We had a really nice ride along the coast. The temperature
was around 17 C and there was barely any wind. It really was optimal as far as
conditions went. I didn&amp;rsquo;t even get a sunburn. Luckily we did make a stop for a
delicious espresso and custard tart. I had lacked the foresight to bring any
kind of food with me, so that made a real difference.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/espresso.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Espresso and a tart for everyone"
width="600"
height="520"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/espresso-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/espresso-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/espresso-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/espresso-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Espresso and a tart for everyone]&lt;/p&gt;
&lt;p&gt;We rode for a long time. Like a really long time. There were long twisty climbs,
long twisty descents and the occasional flat sections. No parts of my body let
me down and I finished without picking up any new injuries (except for the limp
which I sported for the next two days). The 80 km ride ended up being a 110 km
ride, clocking in at 4 hours and 43 minutes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ride.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="My ride on Strava"
width="1121"
height="1950"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ride_hu_df397d057f91ffaa.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ride_hu_df397d057f91ffaa.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ride.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ride.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After the longest bike ride of my life I was not really in the condition to go
for a swim at the beach. Ingy and I headed out to the older part of town to get
some &lt;a href="https://en.wikipedia.org/wiki/Ginjinha" target="_blank"&gt;Ginjinha&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ginjinha.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="ginjinha with cherries"
width="511"
height="600"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ginjinha-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ginjinha-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ginjinha-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/ginjinha-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I then struck out on my own in the older part of town, got myself some custard
tarts and made it back in time to get to the arrival dinner as food was being
ordered. It was nice to see old friends after a one year break and to meet some
new faces as well. On the way home we stopped for ice cream at a shopping mall.
I couldn&amp;rsquo;t wrap my head around the fact that the mall is open until midnight
every night. I&amp;rsquo;m lucky when my local malls are open until 9. Some evenings they
close at 6. The Portuguese way is better.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/arrival-dinner.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="A Portuguese style burger from an Irish style pub"
width="476"
height="600"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/arrival-dinner-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/arrival-dinner-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/arrival-dinner-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/arrival-dinner-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[A Portuguese style burger from an Irish style pub]&lt;/p&gt;
&lt;h2 class="relative group"&gt;Day 0
&lt;div id="day-0" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#day-0" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;On Thursday morning, we started with a short standup meeting and then people
split into groups of one or more to make plans and get hacking.&lt;/p&gt;
&lt;p&gt;Our plan was two-fold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Begin moving more of our infrastructure into the cloud, via Kubernetes&lt;/li&gt;
&lt;li&gt;Begin moving our Elasticsearch data from bare metal to the Elasticsearch
hosted offering.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The reason we are able to move our data to a hosted offering by Elasticsearch is
because &lt;a href="https://www.elastic.co/" target="_blank"&gt;Elastic&lt;/a&gt; is our newest sponsor. I had reached
out to Elastic shortly before PTS to ask if they could help us out. They quickly
responded by saying that they would like to support the Perl community. In the
next day or so I had a meeting with their developer relations contact and we got
immediate access to all of the resources which we required. This is a huge
helping hand for us in keeping the project moving forward. It will allow us to
have better uptime and also will give us better tools for migrating from our
very old &lt;strong&gt;v2.4&lt;/strong&gt; Elasticsearch to a more modern &lt;strong&gt;v8&lt;/strong&gt; version. My personal
thanks to Elastic for making this happen!&lt;/p&gt;
&lt;p&gt;As &lt;a href="https://metacpan.org/author/LLAP" target="_blank"&gt;Leo&lt;/a&gt; and &lt;a href="https://metacpan.org/author/JBERGER" target="_blank"&gt;Joel&lt;/a&gt; got to work on the kubernetes end of things,
&lt;a href="https://metacpan.org/author/HAARG" target="_blank"&gt;Graham&lt;/a&gt; made various code improvements around the indexing of Perl itself,
which requires some special casing. &lt;a href="https://metacpan.org/author/MICKEY" target="_blank"&gt;Mickey&lt;/a&gt; began reworking the indexer
code in order to make a future Elasticsearch upgrade easier.&lt;/p&gt;
&lt;p&gt;I mostly got down to administrative things.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I had to submit a bunch of receipts to Open Collective so that they don&amp;rsquo;t
suspend our virtual credit card.&lt;/li&gt;
&lt;li&gt;I purged some very old data from our S3 buckets so that we&amp;rsquo;re no longer being
billed for hosting it.&lt;/li&gt;
&lt;li&gt;On this day we also became aware that the disk on our primary hosting machine
was in read-only mode. Leo switched the cron jobs to another machine, but our
Minion queue was not running. I spent much of the afternoon debugging this
issue. I was successful and we shut down the old machine. Our 3 machine
cluster at Bytemark was now reduced to 2, but we still had enough redundancy
to get by.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After this I began looking at the Elasticsearch snapshot restore process, but I
just couldn&amp;rsquo;t get it to work on my first attempts.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Day 1
&lt;div id="day-1" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#day-1" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;We started with standup once again and then got to our various tasks. I began
again with trying to restore an existing Elasticsearch snapshot from our
production machine into the Elasticsearch cloud. I was aware that this could be
tricky, since 2.4 is no longer a supported version of Elasticsearch. We were
able to get a 2.4 Elasticsearch in the cloud provided to us, but the caveat was
that it might be tricky to set up, given the amount of intervening years since
it was released. That was entirely fair. I spent some time trying to find the
correct incantation for registering our s3 bucket with Elasticsearch and I was
coming up against slightly cryptic errors which were being proxied back from an
AWS Java library. Leo also sat down with me to try to make some progress.
Eventually we had to admit defeat on that for the day and move on to something
else.&lt;/p&gt;
&lt;p&gt;Unrelated to MetaCPAN, I merged a PR for
&lt;a href="https://github.com/xaicron/p5-www-youtube-download/pull/68" target="_blank"&gt;p5-www-youtube-download&lt;/a&gt;.
That library hasn&amp;rsquo;t worked in a while, but apparently it&amp;rsquo;s now less broken.
However, since some of the tests are still broken, someone may need to look into
that before I can release. I don&amp;rsquo;t use this software at all. I&amp;rsquo;m happy to merge
and release but I don&amp;rsquo;t feel like it&amp;rsquo;s a good use of my time to be spending a
lot of effort on it.&lt;/p&gt;
&lt;p&gt;I sat down and spent some time with &lt;a href="https://metacpan.org/author/ABELTJE" target="_blank"&gt;ABELTJE&lt;/a&gt;, who is moving
&lt;a href="https://test-smoke.org/" target="_blank"&gt;test-smoke.org&lt;/a&gt; into the MetaCPAN infrastructure so
that he is no longer solely responsible for maintaining the site. He was in the
process of Dockerizing the site and was 95% of the way there. Together we ironed
out the final bit and got his Docker containers running locally.&lt;/p&gt;
&lt;p&gt;There was also a discussion about settling the Perl logo question, which
&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/kitharringtonhair.jpg" &gt;NEILB&lt;/a&gt; participated in via Zoom. I think we made some
good headway. We had a number of stakeholders in the room and after some hearty
debate, I&amp;rsquo;m hopeful we can move ahead with this in the near future. Watch this
space.&lt;/p&gt;
&lt;p&gt;I also did a fair bit of code review as Graham works at a furious pace and I
didn&amp;rsquo;t want to slow him down.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Day 2
&lt;div id="day-2" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#day-2" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I believe this day may not have begun with standup. As is tradition, standup
usually gets stood up somewhere along the way as jetlag and late nights catch up
with attendees and they trickle in later and later on subsequent days.&lt;/p&gt;
&lt;p&gt;Since we are looking at a fair amount of future code churn in the metacpan-api
repository, I thought it would be helpful to get code coverage metrics going on
it and metacpan-web. metacpan-web was fairly easy to set up as it runs in GitHub
Actions. It has multiple builds, two of which do not use Docker. metacpan-api,
on the other hand, was a different case. It only runs tests via &lt;code&gt;docker compose&lt;/code&gt;
and that all happens on CircleCI. We do this because CircleCI allows us to
select a machine size (we want as large as possible) and it allows us to re-run
failed builds with an open SSH connection to the container. To be honest, the
SSH thing is a game changer. I absolutely love it and I&amp;rsquo;m not just saying that
because CircleCI mailed me a shirt and socks recently. 👕 🧦 😀&lt;/p&gt;
&lt;p&gt;I did not finish this project, but it was a good start.&lt;/p&gt;
&lt;p&gt;On this day ABELTJE sat with us and I worked with him on automating the build of
his Docker images via GitHub Actions. The actions now push images to the
MetaCPAN Docker Hub account, which is a vital step in deploying test-smoke.org
on his behalf.&lt;/p&gt;
&lt;p&gt;Perhaps the most exciting news is that I finally got the Elasticsearch snapshot
restore working. I had gotten some help via email from the Developer Relations
person at Elastic and this set me on the right path. Basically I needed to
provide an extra key in the JSON I was sending to register the bucket. I don&amp;rsquo;t
think this key was specifically mentioned in the docs, but I stumbled over
something that gave me the idea to try. I&amp;rsquo;m happy to say that we now have a very
large amount of data in the Elastic cloud, which will make it much easier for us
to move ahead with future upgrades.&lt;/p&gt;
&lt;p&gt;My abject failure to meet the moment when it came to writing a few &lt;code&gt;curl&lt;/code&gt;
commands had left me wondering if I had any purpose at all being in the same
room with people who are smart enough that they surely would never find
themselves stymied by such a banal task. I try not to let my professional life
define my self worth, but this one had been messing with my mind. However, now
buoyed by my victory over AWS, Java and a possible gap in documentation, I was
once more fighting fit. I was, indeed, The King of the World.&lt;/p&gt;
&lt;p&gt;Soon it was time to celebrate with food. I like to make it a point to try local
cuisine and, on this evening, a group of us headed out to for a traditional
Portuguese dinner. Joel found us a venue using Google Maps, assuring us with
great confidence that he was quite skilled in this regard. He did, in fact, lead
us to a very nice restaurant. As we were seated it became clear that the
restaurant was Spanish. As we pondered our options for paella, Joel helpfully
pointed out that Spain was at least geographically quite close.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Day 3
&lt;div id="day-3" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#day-3" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;On this day I began by revisiting the test coverage work. We could have moved on
without it, but to be honest, it was really bugging me that I couldn&amp;rsquo;t figure it
out. It took some messing around to get this to work. It&amp;rsquo;s essentially
installing &lt;code&gt;Devel::Cover&lt;/code&gt; inside a Docker container and then running the tests
via &lt;code&gt;docker compose&lt;/code&gt;. Once that is done we install &lt;code&gt;Devel::Cover&lt;/code&gt; and some
libraries required by Codecov outside of Docker. We then generate and upload the
report from outside of the container. It&amp;rsquo;s easier to upload reports from outside
of the container, because there we have all of the CircleCI environment
variables which are required at upload time.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all working nicely now and I&amp;rsquo;m quite happy with it. I&amp;rsquo;m also a little
surprised at some of the code coverage gaps we have. 😅 Kudos to &lt;a href="https://metacpan.org/author/PJCJ" target="_blank"&gt;PJCJ&lt;/a&gt; for
continuing to make this one of the most helpful Perl modules ever.&lt;/p&gt;
&lt;p&gt;A side effect of this work is that I was able to spend some time looking at the
CircleCI docs, comparing their current open source offering with our current
usage. I realized that we could bump our VM size from &lt;code&gt;medium&lt;/code&gt; to &lt;code&gt;large&lt;/code&gt;. This
decreases the build time by about 30%, reducing the build from ~20 minutes to
~14 minutes. There may be some more speed gains to be had, but this was a great
improvement to start with.&lt;/p&gt;
&lt;p&gt;I also spent a bit of time on administrative work, reaching out to TPRF to
secure email hosting for smoke-test.org, as we don&amp;rsquo;t want to email in future
when the domain name is transferred to TPRF. We had transferred the MetaCPAN
domain name to TPRF a number of years ago in order to reduce the bus number on
our project. I think that was a good approach.&lt;/p&gt;
&lt;p&gt;I also began the work of configuring the API to talk to the new Elasticsearch in
the cloud. Currently the API allows for the Elasticsearch connection to be set
via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a series of Apache-style config files which are merged via &lt;code&gt;Config::ZOMG&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a series of &lt;code&gt;.pl&lt;/code&gt; files which are &lt;code&gt;evaled&lt;/code&gt; and then merged into a &lt;code&gt;HashRef&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;some hardcoded defaults&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s kind of fun. These things evolve organically over time and there are good
reasons for all of them to exist, but that doesn&amp;rsquo;t make them easy to work with
or necessarily easy to understand. I began tearing down a lot of this config and
moving everything to the Apache-style configs which Catalyst is expecting. I
don&amp;rsquo;t love this, but it&amp;rsquo;s better to have this logic in one place. I ended up
with a giant &amp;ldquo;Work in Progress&amp;rdquo; commit which I pushed to GitHub. It breaks
things and I haven&amp;rsquo;t figured all of it out yet. Over the next little while I&amp;rsquo;ll
probably tease that apart into smaller chunks that can be committed as a series
of pull requests. I wasn&amp;rsquo;t all that hopeful that a scorched earth approach would
&amp;ldquo;just work&amp;rdquo;, but you never know. I&amp;rsquo;m generally encouraged by the progress of
this work, though.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Sponsors
&lt;div id="the-sponsors" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-sponsors" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;This event wouldn&amp;rsquo;t take place without the financial backing of our wonderful
sponsors. I&amp;rsquo;d like to thank the following donors for making this event a
reality:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.booking.com/" target="_blank"&gt;Booking.com&lt;/a&gt;,
&lt;a href="https://www.perlfoundation.org/" target="_blank"&gt;The Perl and Raku Foundation&lt;/a&gt;,
&lt;a href="https://deriv.com/?utm_source=perltoolchainsummitdotorg&amp;amp;utm_medium=pr&amp;amp;utm_campaign=sponsorship&amp;amp;utm_content=homepage_logo" target="_blank"&gt;Deriv&lt;/a&gt;,
&lt;a href="https://cpanel.net/" target="_blank"&gt;cPanel, Inc&lt;/a&gt;,
&lt;a href="https://japan.perlassociation.org/" target="_blank"&gt;Japan Perl Association&lt;/a&gt;,
&lt;a href="https://www.perl-services.de/" target="_blank"&gt;Perl-Services&lt;/a&gt;,
&lt;a href="https://www.simplelists.com/" target="_blank"&gt;Simplelists Ltd&lt;/a&gt;,
&lt;a href="https://www.ctrlo.com/" target="_blank"&gt;Ctrl O Ltd&lt;/a&gt;,
&lt;a href="https://www.findus-internet-opac.de/" target="_blank"&gt;Findus Internet-OPAC&lt;/a&gt;, Harald Joerg,
Steven Schubiger. In kind sponsors: &lt;a href="https://www.fastmail.com/" target="_blank"&gt;Fastmail&lt;/a&gt;,
&lt;a href="https://www.grantstreet.com/" target="_blank"&gt;Grant Street Group&lt;/a&gt;, &lt;a href="https://deft.com/" target="_blank"&gt;Deft&lt;/a&gt;,
&lt;a href="https://www.procura.nl/" target="_blank"&gt;Procura&lt;/a&gt;, &lt;a href="https://healex.systems/" target="_blank"&gt;Healex GmbH&lt;/a&gt;,
&lt;a href="https://www.suse.com/" target="_blank"&gt;SUSE&lt;/a&gt;, &lt;a href="https://www.zoopla.co.uk/" target="_blank"&gt;Zoopla&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Attendees
&lt;div id="the-attendees" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-attendees" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As pictured below, this year we had &lt;a href="https://metacpan.org/author/LLAP" target="_blank"&gt;Leo Lapworth&lt;/a&gt;, &lt;a href="https://metacpan.org/author/HAARG" target="_blank"&gt;Graham Knop&lt;/a&gt;, &lt;a href="https://metacpan.org/author/OALDERS" target="_blank"&gt;Olaf
Alders&lt;/a&gt; and &lt;a href="https://metacpan.org/author/MICKEY" target="_blank"&gt;Mickey Nasriachi&lt;/a&gt; in attendance to work on MetaCPAN. Not
pictured is &lt;a href="https://metacpan.org/author/JBERGER" target="_blank"&gt;Joel Berger&lt;/a&gt;. He&amp;rsquo;s missing because this picture is from 2023.
&lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We once again missed &lt;a href="https://metacpan.org/author/SSORICHE" target="_blank"&gt;Shawn Sorichetti&lt;/a&gt; very much, as he was unable to
attend.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/05/01/how-i-spent-my-perl-toolchain-summit-v2023/group2.jpg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="the MetaCPAN team"
src="https://www.olafalders.com/2023/05/01/how-i-spent-my-perl-toolchain-summit-v2023/group2.jpg"
&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Photo © Salve J. Nilsen, 2023, CC-BY-NC-4.0&lt;/p&gt;
&lt;h2 class="relative group"&gt;Welcome to Our Newest Sponsor
&lt;div id="welcome-to-our-newest-sponsor" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#welcome-to-our-newest-sponsor" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As mentioned above, &lt;a href="https://www.elastic.com" target="_blank"&gt;Elastic&lt;/a&gt; recently joined us as a
sponsor. If your company would like to help us continue to make finding Perl
modules enjoyable, please do
&lt;a href="https://opencollective.com/metacpan-core" target="_blank"&gt;have a look at our sponsorship opportunities&lt;/a&gt;
and reach out to us at &lt;a href="mailto:noc@metacpan.org" &gt;noc@metacpan.org&lt;/a&gt; with any
questions.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Cascais
&lt;div id="cascais" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cascais" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/cascais.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="The beach at Cascais"
width="600"
height="450"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/cascais-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/cascais-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/cascais-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/cascais-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Around 5 PM on the Sunday evening I headed out to Cascais to go for a swim. I
had seen the beach on our bike ride and I was excited to visit. The air and
water temperature were 17 C. That&amp;rsquo;s still a bit chilly in the water, but
sometimes that&amp;rsquo;s as good as it gets in Lake Ontario even on a summer day.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/swim.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="My swim on Strava"
width="1124"
height="1623"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/swim_hu_6f82ee81e87ada4a.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/swim_hu_6f82ee81e87ada4a.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/swim.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/swim.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I hadn&amp;rsquo;t packed a wetsuit, but I managed around 35 minutes in the water before
it became clear that it was time to get out. It was a great experience. I got to
feel the salt water in my mouth and on my skin, in addition to taking in some
lovely scenery from the water. I was a bit numb when I got out, but eventually
the feeling completely returned to my feet. It only required about an hour of
waiting, followed by a hot bath.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Heading Home
&lt;div id="heading-home" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#heading-home" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;On Monday morning Leo and I headed across the street to buy a lot of custard
tarts to take home. I should note that I ate custard tarts most days that I was
there. I ate more than one per day. I even ate them fresh from the shop, when
the pastry was particularly flaky and the custard had not yet fully set. It was
a special kind of delicious and far better than the tarts I&amp;rsquo;ve been getting at
home. I also learned that you can take them up to the next level by sprinkling a
little bit of cinnamon on top. That&amp;rsquo;s possibly one of the most important things
I learned on this trip.&lt;/p&gt;
&lt;p&gt;Leo, ABELTJE, &lt;a href="https://metacpan.org/author/LEONT" target="_blank"&gt;LEONT&lt;/a&gt; and I headed to the airport around 10 AM. I then got
on a plane to CDG in Paris where I spent 3 hours eating pastries, chocolate
mousse and browsing. After that it was on to YYZ, arriving around 9 PM and
heading back to my normal routine, which included insisting that my kids eat
custard tarts before going to bed.&lt;/p&gt;
&lt;p&gt;Looking back on the week, I realized that I never had time to deal with (or even
acknowledge) jetlag after arrival, because all of the days were filled with
activities. On the return trip I started work the next morning and just got back
to life as usual. For whatever reason it just worked out. I had one 45 minute
nap, face down on my office floor, but other than that no issues. I have no idea
why.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/wheels-down.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="Wheels down"
width="600"
height="450"
src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/wheels-down-600.jpeg"
srcset="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/wheels-down-600.jpeg 800w, https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/wheels-down-600.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/wheels-down-600.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;That&amp;rsquo;s a Wrap
&lt;div id="thats-a-wrap" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#thats-a-wrap" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I want to thank &lt;a href="https://metacpan.org/author/GARU" target="_blank"&gt;Breno&lt;/a&gt;, &lt;a href="https://metacpan.org/author/BOOK" target="_blank"&gt;Philippe&lt;/a&gt;, &lt;a href="https://metacpan.org/author/LBOIVIN" target="_blank"&gt;Laurent&lt;/a&gt; and &lt;a href="https://metacpan.org/author/NEILB" target="_blank"&gt;Neil&lt;/a&gt;, for
all of their work in organising this event. It was incredibly well organized.
Portugal had not been on my short list of places to visit, but I&amp;rsquo;m so glad that
I was able to make this trip. It was well worth it.&lt;/p&gt;
&lt;p&gt;Aside from having had a most excellent time, I&amp;rsquo;m mentally in a much better place
than before I attended. I wasn&amp;rsquo;t in a bad place before, but I&amp;rsquo;m enjoying my work
more right now and I&amp;rsquo;m enjoying chipping away at MetaCPAN. Collectively, we&amp;rsquo;ve
maintained some momentum since PTS has ended and we&amp;rsquo;re continuing to get work
done. It&amp;rsquo;s one of those side effects of conferences like these. Hopefully you
get good work done at the conference, but sometimes the work leading up to and
following on from the meetup can be just as important.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/21/how-i-spent-my-perl-toolchain-summit-v2024/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description></item><item><title>One Line Fuzzy Find for Git Worktree</title><link>https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/</link><pubDate>Fri, 14 Jun 2024 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="splash"
width="1128"
height="1128"
src="https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/featured_hu_3c1ec0f8b41e08f6.jpeg"
srcset="https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/featured_hu_3c1ec0f8b41e08f6.jpeg 800w, https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/57985854@N08/8208677475"&gt;Magnifying Glass&lt;/a&gt;" by &lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/57985854@N08"&gt;Birmingham Conservation Trust&lt;/a&gt; is licensed under &lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC BY 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Backstory
&lt;div id="the-backstory" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-backstory" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve written on fuzzy matching with &lt;code&gt;fzf&lt;/code&gt; before. I&amp;rsquo;ve covered
&lt;a href="https://www.olafalders.com/2021/07/13/prove-tab-completion/" &gt;adding tab completion to an existing command&lt;/a&gt;,
&lt;a href="https://www.olafalders.com/2021/07/15/tab-completion-with-preview/" &gt;using fzf to get a file preview windows&lt;/a&gt;
and
&lt;a href="https://www.olafalders.com/2021/08/12/prove-tab-completion-with-preview/" &gt;combining tab completion and preview windows to improve an existing command&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Those are helpful things to do and I use them all the time, but today I&amp;rsquo;d like
to cover a much simpler use case &amp;ndash; a one-liner to allow you to use fuzzy
matching to navigate &lt;code&gt;git&lt;/code&gt; worktrees. If you&amp;rsquo;d like to read up on why you may
want to use worktrees in &lt;code&gt;git&lt;/code&gt;, I&amp;rsquo;ve covered that in
&lt;a href="https://www.olafalders.com/2024/01/09/git-context-switching/" &gt;4 Strategies for Context Switching in Git&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Just Show Me the Command
&lt;div id="just-show-me-the-command" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#just-show-me-the-command" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;ve heard enough from me by this point. Let&amp;rsquo;s try the command below. If
you&amp;rsquo;re playing along at home, you&amp;rsquo;ll need to be inside a &lt;code&gt;git&lt;/code&gt; repository.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: if you&amp;rsquo;re in a repository where you have not explicitly created a
worktree, you&amp;rsquo;ll only get one result in the list, since git repositories by
default exist in a worktree.&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git worktree list &lt;span class="p"&gt;|&lt;/span&gt; fzf &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you have more than one worktree in your repository, you should see something
like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /Users/olaf/.worktree/Perl-Advent/oalders/santa-picks-nits c312df9 [oalders/santa-picks-nits]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /Users/olaf/.worktree/Perl-Advent/oalders/perlimports a8884db [oalders/perlimports]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt; /Users/olaf/Documents/github/perladvent/Perl-Advent 2a3d745 [oalders/new-index.html]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3/3 (0) ───────────────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Once you begin typing, the matching begins:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /Users/olaf/.worktree/Perl-Advent/oalders/perlimports a8884db [oalders/perlimports]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /Users/olaf/Documents/github/perladvent/Perl-Advent 2a3d745 [oalders/new-index.html]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt; /Users/olaf/.worktree/Perl-Advent/oalders/santa-picks-nits c312df9 [oalders/santa-picks-nits]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3/3 (0) ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; nit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;At this point a &lt;code&gt;return&lt;/code&gt; will &lt;code&gt;cd&lt;/code&gt; you to the worktree of your choice, or you
can opt out via &lt;code&gt;ctrl-c&lt;/code&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Let it Live in a Function
&lt;div id="let-it-live-in-a-function" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#let-it-live-in-a-function" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;To make this easier to use, you can store it in a function:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cd_worktree&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git worktree list &lt;span class="p"&gt;|&lt;/span&gt; fzf &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If this is in your &lt;code&gt;.bashrc&lt;/code&gt; or some other place that gets sourced by your
environment, you can have access to this selector via the function name directly
at the command line:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cd_worktree&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;Or Put it in a Script
&lt;div id="or-put-it-in-a-script" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#or-put-it-in-a-script" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;This works too:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -eu -o pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git worktree list &lt;span class="p"&gt;|&lt;/span&gt; fzf &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;How About an alias?
&lt;div id="how-about-an-alias" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-about-an-alias" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;You might find a way to mash this into an &lt;code&gt;alias&lt;/code&gt;, but I should point out the
following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;a href="https://ss64.com/bash/alias.html" target="_blank"&gt;bash man page&lt;/a&gt; says that&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote &gt;&lt;p&gt;For almost every purpose, shell functions are preferred over aliases&lt;/p&gt;
&lt;/blockquote&gt;&lt;ol start="2"&gt;
&lt;li&gt;You&amp;rsquo;ll likely have a linter complaining about a positional parameter in an
alias. And yes, I realize that the &lt;code&gt;$1&lt;/code&gt; here is an &lt;code&gt;awk&lt;/code&gt; thing rather than a
&lt;code&gt;bash&lt;/code&gt; thing, but my linter doesn&amp;rsquo;t know that.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Having said that, how you handle this is entirely up to you. If someone tells
that using an alias means you&amp;rsquo;re wrong, you can kindly tell them to get stuffed,
because there&amp;rsquo;s certainly more than one way to do it.&lt;/p&gt;
&lt;h2 class="relative group"&gt;How it Works
&lt;div id="how-it-works" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-works" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s break this down. Basically there are 4 things happening here:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git worktree list &lt;span class="p"&gt;|&lt;/span&gt; fzf &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git worktree list&lt;/code&gt; is being called&lt;/li&gt;
&lt;li&gt;All of the output from &lt;code&gt;git worktree list&lt;/code&gt; is piped directly to &lt;code&gt;fzf&lt;/code&gt;. At
this point nothing happens until there is some input from the keyboard&lt;/li&gt;
&lt;li&gt;Once there is a carriage &lt;code&gt;&amp;lt;return&amp;gt;&lt;/code&gt;, the entire selected line is handed off
to &lt;code&gt;awk&lt;/code&gt;, which splits the line on spaces and returns the first element in
the list (&lt;code&gt;$1&lt;/code&gt;). That element is the path to the root directory of the
worktree.&lt;/li&gt;
&lt;li&gt;The worktree path is provided to the &lt;code&gt;cd&lt;/code&gt; command and now you are on your way&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 class="relative group"&gt;Build Something Fun
&lt;div id="build-something-fun" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#build-something-fun" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;What I like about this example is that it&amp;rsquo;s not particularly complicated, but it
has enough moving parts that it merits some discussion. You could build
something similar for switching between &lt;code&gt;git&lt;/code&gt; branches, &lt;code&gt;tmux&lt;/code&gt; sessions or
something way better that I haven&amp;rsquo;t even thought of.&lt;/p&gt;
&lt;p&gt;Because it fits on one line you can even keep it as a handy snippet that you
copy/paste, if you don&amp;rsquo;t feel like making shell functions available or putting
the one-liner into a script or whatever.&lt;/p&gt;
&lt;p&gt;I hope you have fun with this. Please &lt;a href="https://www.olafalders.com/about-me" &gt;let me know&lt;/a&gt; if you build
something you&amp;rsquo;d like to share with me.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2024/06/14/one-line-fuzzy-find-for-git-worktree/featured.jpeg"/></item><item><title>Getting Started with perlimports</title><link>https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/</link><pubDate>Mon, 15 Apr 2024 10:00:00 +0000</pubDate><guid>https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/</guid><description>&lt;p&gt;&lt;em&gt;A version of this post was previously published
&lt;a href="https://perladvent.org/2023/2023-12-23.html" target="_blank"&gt;in the 2023 Perl Advent Calendar&lt;/a&gt;.
I have significantly revised it for publication here.&lt;/em&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/splash.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="splash"
width="1024"
height="1024"
src="https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/splash_hu_2fae8a6d67b34c5d.jpeg"
srcset="https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/splash_hu_2fae8a6d67b34c5d.jpeg 800w, https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/splash.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/splash.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;What&amp;rsquo;s in a Package, Anyway?
&lt;div id="whats-in-a-package-anyway" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#whats-in-a-package-anyway" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Perl, like many other languages, exposes the functionality to export functions
(and other things) from one package into another. The functions that are
exported by package A are the same functions that are imported into package B.
Consider the following code:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-perl" data-lang="perl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;WhereAmI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="mf"&gt;5.38.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Git::Helpers&lt;/span&gt; &lt;span class="sx"&gt;qw( checkout_root )&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="n"&gt;checkout_root&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;# or WhereAmI::checkout_root()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this example, &lt;code&gt;Git::Helpers&lt;/code&gt; is &lt;em&gt;exporting&lt;/em&gt; the &lt;code&gt;checkout_root&lt;/code&gt; function into
&lt;code&gt;WhereAmI&lt;/code&gt;. Conversely, &lt;code&gt;WhereAmI&lt;/code&gt; is &lt;em&gt;importing&lt;/em&gt; the &lt;code&gt;checkout_root&lt;/code&gt; function
from &lt;code&gt;Git::Helpers&lt;/code&gt;. Importing and exporting are two sides of the same coin. The
important thing to note is we now have a new function available in the
&lt;code&gt;WhereAmI&lt;/code&gt; package.&lt;/p&gt;
&lt;p&gt;Today we&amp;rsquo;ll mostly be referring to &lt;em&gt;imports&lt;/em&gt;, but in many cases we could just as
well be framing our discussion in terms of &lt;em&gt;exports&lt;/em&gt; instead.&lt;/p&gt;
&lt;h2 class="relative group"&gt;What is perlimports?
&lt;div id="what-is-perlimports" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-is-perlimports" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I have been interested in code quality for many years. This is something I
touched on in &lt;a href="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos" &gt;Find and Fix More Typos&lt;/a&gt; and
&lt;a href="https://www.olafalders.com/2022/02/22/finding-unused-perl-variables/" &gt;Finding Unused Perl Variables&lt;/a&gt;. As
part of that interest, I wrote a tool to help automate cleaning up cases where
code is being imported. This tool is called
&lt;a href="https://metacpan.org/dist/App-perlimports/view/script/perlimports" target="_blank"&gt;perlimports&lt;/a&gt;.
&lt;code&gt;perlimports&lt;/code&gt; is a tidier, which means it can rewrite our code for us. It&amp;rsquo;s also
a linter, so we can use it to report on problems without rewriting our code.
With this power comes great responsibility. &lt;code&gt;perlimports&lt;/code&gt; tries to be
responsible.&lt;/p&gt;
&lt;p&gt;Inspired by &lt;a href="https://pkg.go.dev/golang.org/x/tools/cmd/goimports" target="_blank"&gt;goimports&lt;/a&gt;,
&lt;code&gt;perlimports&lt;/code&gt; is an opinionated tool which tries to force its opinions onto our
code. What we get in return is a tool which takes much of the burden of managing
imports out of our hands.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you&amp;rsquo;re interested in a more thorough discussion of how &lt;code&gt;perlimports&lt;/code&gt; and
Perl&amp;rsquo;s &lt;code&gt;use&lt;/code&gt; and &lt;code&gt;require&lt;/code&gt; work, I spoke about this in depth at The Perl
Conference in 2021:
&lt;a href="https://www.youtube.com/watch?v=fKqxdTbGxYY" target="_blank"&gt;perlimports or &amp;ldquo;Where did that symbol come from?&amp;rdquo;&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;Why Tidy Imports at all?
&lt;div id="why-tidy-imports-at-all" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-tidy-imports-at-all" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;You may quite correctly be asking yourself if &lt;code&gt;perlimports&lt;/code&gt; is worth the bother.
Why even bother? After having used this tool in anger for about 3 years now,
I&amp;rsquo;ll try to sum up the best points.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Consistent Style
&lt;div id="consistent-style" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#consistent-style" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Just like any other tidier, using &lt;code&gt;perlimports&lt;/code&gt; will automate and enforce a
consistent style for your imports. This has the following advantages:&lt;/p&gt;
&lt;h4 class="relative group"&gt;Code is Easier to Read
&lt;div id="code-is-easier-to-read" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#code-is-easier-to-read" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;When your imports are laid out in a consistent manner, your code can become
easier to scan. You&amp;rsquo;ll have consistent spacing, the imports for a given module
will be alpha-sorted and you&amp;rsquo;ll have a standard quoting style. This should
reduce the cognitive load when you&amp;rsquo;re looking at the first few lines of your
files.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Diffs Become Easier to Read
&lt;div id="diffs-become-easier-to-read" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#diffs-become-easier-to-read" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Once code layout is consistent, code changes can become easier to read, since
you&amp;rsquo;re less likely to be confronted with formatting changes. This can also
reduce the size of your diffs, which also reduces cognitive load.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Code Reviews are Easier
&lt;div id="code-reviews-are-easier" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#code-reviews-are-easier" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Hopefully code reviewers will no longer have to comment on how someone used the
wrong quotes or how they imported something they&amp;rsquo;re not actually using. If your
linting and tidying is automated, code contributors can now also stop worrying
about these little things.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Writing Code is Less Onerous
&lt;div id="writing-code-is-less-onerous" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#writing-code-is-less-onerous" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Once you have a tidier to do the work for you, you can be a fair bit sloppier
when writing code. Write it quickly, get it out of your system and then tidy the
file and watch the magic happen. Not having to worry about the mundane frees up
mental energy for other things.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Improved Dependency Management
&lt;div id="improved-dependency-management" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#improved-dependency-management" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Once you start automatically removing from your code the imports (and even the
modules) which you are not using, it should be easier for you to evaluate which
dependencies you actually need. You may find that you&amp;rsquo;ve been installing modules
which you don&amp;rsquo;t actually use. Fewer dependencies leads to a better security
posture (since you&amp;rsquo;ve reduced your attack surface), faster setup time (because
you now have fewer things to install) and fewer headaches around not being to
install this or that module in a certain environment. Dependencies are great
when you need them, but limiting your dependencies can also have some real
benefits. Unfortunately, linting Perl&amp;rsquo;s imports is not always as easy as we&amp;rsquo;d
like. Let&amp;rsquo;s try to contextualize the problem by taking a quick look at the tools
module authors might use to export code into yours.&lt;/p&gt;
&lt;h2 class="relative group"&gt;There&amp;rsquo;s More Than One Way to Do It
&lt;div id="theres-more-than-one-way-to-do-it" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#theres-more-than-one-way-to-do-it" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;ll allow a small digression, Perl&amp;rsquo;s &lt;code&gt;import()&lt;/code&gt; is an incredibly powerful
tool that does little in the way of imposing restrictions on the implementor
when it is run. This means that over the years CPAN authors have allowed
themselves to be very creative when it comes to what may or may not get imported
into our code.&lt;/p&gt;
&lt;p&gt;This liberal approach to code import has also allowed for the proliferation of
modules which authors can use to manage what their code exports into ours.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Exporter" target="_blank"&gt;https://metacpan.org/pod/Exporter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Sub::Exporter" target="_blank"&gt;https://metacpan.org/pod/Sub::Exporter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Exporter::Tiny" target="_blank"&gt;https://metacpan.org/pod/Exporter::Tiny&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Export::Declare" target="_blank"&gt;https://metacpan.org/pod/Export::Declare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Exporter::Auto" target="_blank"&gt;https://metacpan.org/pod/Exporter::Auto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Exporter::Easy" target="_blank"&gt;https://metacpan.org/pod/Exporter::Easy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Exporter::Lite" target="_blank"&gt;https://metacpan.org/pod/Exporter::Lite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not an exhaustive list, but it&amp;rsquo;s also the kind of TIMTOWTDI that might
leave a Perl newbie scratching their head in confusion and wonder.&lt;/p&gt;
&lt;p&gt;The upshot of all of these different methods of exporting is that a static code
analysis is not an effective way of discovering what is being imported into your
code. &lt;code&gt;perlimports&lt;/code&gt; needs to &lt;code&gt;eval&lt;/code&gt; the package imports and then (in many cases)
see what actually changed in the
&lt;a href="https://perldoc.perl.org/perlmod#Symbol-Tables" target="_blank"&gt;symbol table&lt;/a&gt;. This means two
things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to install dependencies and be able to &lt;code&gt;eval&lt;/code&gt; them in order to see
what&amp;rsquo;s going on&lt;/li&gt;
&lt;li&gt;A speed penalty is imposed on &lt;code&gt;perlimports&lt;/code&gt; for this approach&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;Strategy
&lt;div id="strategy" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#strategy" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As we saw above, there is an embarrassment of riches when it comes to modules to
manage code exports. Some of these exporters work in very different ways, which
makes the job of &lt;code&gt;perlimports&lt;/code&gt; tricky. If we accept that it can be difficult to
predict what some code is &lt;em&gt;actually&lt;/em&gt; importing, we will probably want to be very
careful when rewriting imports. The more &amp;ldquo;important&amp;rdquo; the code is, the more
careful we&amp;rsquo;ll want to be. &lt;code&gt;perlimports&lt;/code&gt; makes a best effort, but it simply
cannot be correct in every case.&lt;/p&gt;
&lt;p&gt;I find that a good approach is to start with lower impact code and move on from
there, once we&amp;rsquo;ve established that we&amp;rsquo;re on a good path. For example, we could
first run &lt;code&gt;perlimports&lt;/code&gt; as a linter, just to see what it might change. Or we
could get some code which is under version control and run &lt;code&gt;perlimports&lt;/code&gt; as a
tidier on it and just &lt;code&gt;diff&lt;/code&gt; the code to see what has changed.&lt;/p&gt;
&lt;p&gt;I would probably start by applying &lt;code&gt;perltidy&lt;/code&gt; to the test suite (assuming it
exists) and then running the tests to see what the impact is. From there I might
move to some code which has good test coverage. Eventually I would make my over
to other code which is lacking in coverage and/or is harder to unit test.
Working from lower to higher impact code allows for a gentle introduction of our
new linting and tidying tool. We&amp;rsquo;ll explore this approach in more detail below,
after we cover installation.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Installation
&lt;div id="installation" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#installation" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at a typical getting started workflow. First we&amp;rsquo;ll install the
package from CPAN. I like to use &lt;a href="https://metacpan.org/pod/App::cpm" target="_blank"&gt;https://metacpan.org/pod/App::cpm&lt;/a&gt;, but feel
free to use your CPAN installer of choice:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; cpm install -g App::perlimports
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(If you&amp;rsquo;re using &lt;a href="https://github.com/tokuhirom/plenv" target="_blank"&gt;https://github.com/tokuhirom/plenv&lt;/a&gt; to manage your Perl
installations, don&amp;rsquo;t forget to run &lt;code&gt;plenv rehash&lt;/code&gt; after &lt;code&gt;cpm install&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Once that is done, we are ready to try &lt;code&gt;perlimports&lt;/code&gt;. The quick way to run it on
a new repository might look something like this:&lt;/p&gt;
&lt;h2 class="relative group"&gt;Getting Started
&lt;div id="getting-started" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#getting-started" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Clone a repo&lt;/li&gt;
&lt;li&gt;Install *all* of the repository&amp;rsquo;s dependencies (including recommended)&lt;/li&gt;
&lt;li&gt;Run the test suite&lt;/li&gt;
&lt;li&gt;Ensure all of the tests are passing. If there are test failures, fix those
first&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;perlimports&lt;/code&gt; with the &lt;code&gt;--lint&lt;/code&gt; flag to see what changes it might make&lt;/li&gt;
&lt;li&gt;Tweak the configuration until we&amp;rsquo;re happy with the linting results&lt;/li&gt;
&lt;li&gt;Apply &lt;code&gt;perlimports&lt;/code&gt; to the tests. We can do this via &lt;code&gt;perlimports -i t&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ensure all of the tests are still passing&lt;/li&gt;
&lt;li&gt;Commit the changes&lt;/li&gt;
&lt;li&gt;Move on to other parts of the code, e.g.: &lt;code&gt;perlimports -i lib&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a best case scenario, this &amp;ldquo;just works&amp;rdquo;. Let&amp;rsquo;s try it on a really old
repository of mine.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git clone https://github.com/oalders/acme-odometer.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; acme-odometer/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cpm install -g --with-recommends --cpanfile cpanfile
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ yath t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ perlimports --lint t&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I&amp;rsquo;m not including the command output, but I ran this locally and all of the
steps &amp;ldquo;just worked&amp;rdquo;. I ran the test(s) via
&lt;a href="https://metacpan.org/module/yath" target="_blank"&gt;yath&lt;/a&gt; and they passed. Why did I use
&lt;a href="https://metacpan.org/module/yath" target="_blank"&gt;yath&lt;/a&gt; rather than &lt;code&gt;make test&lt;/code&gt; or
&lt;a href="https://metacpan.org/module/prove" target="_blank"&gt;prove&lt;/a&gt;? I certainly could have done either
of those things, but I&amp;rsquo;m trying to get in the habit of using more modern tools.
&lt;a href="https://metacpan.org/module/yath" target="_blank"&gt;yath&lt;/a&gt; comes bundled with
&lt;a href="https://metacpan.org/module/Test2::Harness" target="_blank"&gt;Test2::Harness&lt;/a&gt;. If you&amp;rsquo;d like to
learn more about some of the features which
&lt;a href="https://metacpan.org/module/Test2" target="_blank"&gt;Test2&lt;/a&gt; provides, please see
&lt;a href="https://perladvent.org/2023/2023-12-15.html" target="_blank"&gt;Santa’s Workshop Secrets: The Magical Test2 Suite (Part 1)&lt;/a&gt;
and
&lt;a href="https://perladvent.org/2023/2023-12-16.html" target="_blank"&gt;Santa’s Workshop Secrets: The Magical Test2 Suite (Part 2)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s see what the linting looks like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ perlimports --lint t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ Test::Most &lt;span class="o"&gt;(&lt;/span&gt;import arguments need tidying&lt;span class="o"&gt;)&lt;/span&gt; at t/load.t line &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -1 +1 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Test::Most&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Test::Most &lt;span class="nv"&gt;import&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt; qw&lt;span class="o"&gt;(&lt;/span&gt; done_testing ok &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ Acme::Odometer &lt;span class="o"&gt;(&lt;/span&gt;import arguments need tidying&lt;span class="o"&gt;)&lt;/span&gt; at t/load.t line &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -3 +3 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Acme::Odometer&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Acme::Odometer &lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ Path::Class &lt;span class="o"&gt;(&lt;/span&gt;import arguments need tidying&lt;span class="o"&gt;)&lt;/span&gt; at t/load.t line &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -4 +4 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Path::Class qw&lt;span class="o"&gt;(&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Path::Class qw&lt;span class="o"&gt;(&lt;/span&gt; file &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can see three suggestions have been made. In the first suggestion,
&lt;code&gt;perlimports&lt;/code&gt; has detected that &lt;code&gt;done_testing&lt;/code&gt; and &lt;code&gt;ok&lt;/code&gt; are the only functions
exported by &lt;a href="https://metacpan.org/module/Test::Most" target="_blank"&gt;Test::Most&lt;/a&gt; which the test
is using, so it has made this explicit.&lt;/p&gt;
&lt;p&gt;In the second suggestion &lt;code&gt;perlimports&lt;/code&gt; has detected that the test is not
importing any symbols from
&lt;a href="https://metacpan.org/module/Acme::Odometer" target="_blank"&gt;Acme::Odometer&lt;/a&gt;, so it has made
this explicit by adding the empty round parens following the &lt;code&gt;use&lt;/code&gt; statement.&lt;/p&gt;
&lt;p&gt;In the third suggestion we see that some whitespace padding has been added to
the &lt;a href="https://metacpan.org/module/Path::Class" target="_blank"&gt;Path::Class&lt;/a&gt; import.&lt;/p&gt;
&lt;p&gt;If we don&amp;rsquo;t like these changes, we can tweak the configuration. To tell
&lt;code&gt;perlimports&lt;/code&gt; to ignore &lt;a href="https://metacpan.org/module/Test::Most" target="_blank"&gt;Test::Most&lt;/a&gt;, we
can change our incantation:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;perlimports --lint --ignore-modules Test::Most t&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If we also don&amp;rsquo;t like the additional padding, we can turn that off:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;perlimports --lint --ignore-modules Test::Most --no-padding t&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Applying these settings we now get:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ perlimports --lint --ignore-modules Test::Most --no-padding t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ Acme::Odometer &lt;span class="o"&gt;(&lt;/span&gt;import arguments need tidying&lt;span class="o"&gt;)&lt;/span&gt; at t/load.t line &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -3 +3 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Acme::Odometer&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Acme::Odometer &lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It&amp;rsquo;s time to update the actual file. We&amp;rsquo;ll use &lt;code&gt;-i&lt;/code&gt; for an inplace edit:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;perlimports -i --ignore-modules Test::Most --no-padding t&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The result is:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git --no-pager diff t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;diff --git a/t/load.t b/t/load.t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;index 503d560..d19688f &lt;span class="m"&gt;100644&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--- a/t/load.t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+++ b/t/load.t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -1,6 +1,6 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; use Test::Most&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Acme::Odometer&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Acme::Odometer &lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; use Path::Class qw&lt;span class="o"&gt;(&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; file&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;assets&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;odometer&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;-&amp;gt;stringify&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Are the tests still passing?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yath t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Defaulting to the &lt;span class="s1"&gt;&amp;#39;test&amp;#39;&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; **
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt; PASSED &lt;span class="o"&gt;)&lt;/span&gt; job &lt;span class="m"&gt;1&lt;/span&gt; t/load.t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                                Yath Result Summary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-----------------------------------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     File Count: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Assertion Count: &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      Wall Time: 1.00 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       CPU Time: 1.42 seconds &lt;span class="o"&gt;(&lt;/span&gt;usr: 0.32s &lt;span class="p"&gt;|&lt;/span&gt; sys: 0.09s &lt;span class="p"&gt;|&lt;/span&gt; cusr: 0.77s &lt;span class="p"&gt;|&lt;/span&gt; csys: 0.24s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      CPU Usage: 142%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    --&amp;gt; Result: PASSED &amp;lt;--&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Excellent. Let&amp;rsquo;s add the changes via &lt;code&gt;git&lt;/code&gt; and commit them. After that, let&amp;rsquo;s
turn to the &lt;code&gt;lib&lt;/code&gt; directory.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ perlimports --lint --ignore-modules Test::Most --no-padding lib
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ namespace::clean &lt;span class="o"&gt;(&lt;/span&gt;appears to be unused and should be removed&lt;span class="o"&gt;)&lt;/span&gt; at lib/Acme/Odometer.pm line &lt;span class="m"&gt;9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -9 +8,0 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use namespace::clean&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ GD &lt;span class="o"&gt;(&lt;/span&gt;import arguments need tidying&lt;span class="o"&gt;)&lt;/span&gt; at lib/Acme/Odometer.pm line &lt;span class="m"&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -11 +11 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use GD&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use GD &lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ Memoize &lt;span class="o"&gt;(&lt;/span&gt;import arguments need tidying&lt;span class="o"&gt;)&lt;/span&gt; at lib/Acme/Odometer.pm line &lt;span class="m"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -12 +12 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Memoize&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Memoize qw&lt;span class="o"&gt;(&lt;/span&gt;memoize&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❌ Path::Class &lt;span class="o"&gt;(&lt;/span&gt;import arguments need tidying&lt;span class="o"&gt;)&lt;/span&gt; at lib/Acme/Odometer.pm line &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -14 +14 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Path::Class qw&lt;span class="o"&gt;(&lt;/span&gt; file &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Path::Class qw&lt;span class="o"&gt;(&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now, we already see some issues. First off, &lt;code&gt;perlimports&lt;/code&gt; doesn&amp;rsquo;t seem to know
about &lt;a href="https://metacpan.org/module/namespace::clean" target="_blank"&gt;namespace::clean&lt;/a&gt;. That&amp;rsquo;s
ok. We can ignore it.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;perlimports --lint --ignore-modules namespace::clean,Test::Most &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --no-padding lib&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As an aside, we could also update the code to use
&lt;a href="https://metacpan.org/module/namespace::autoclean" target="_blank"&gt;namespace::autoclean&lt;/a&gt; while
we&amp;rsquo;re poking around, but we&amp;rsquo;re trying to make minimal changes in this first
iteration.&lt;/p&gt;
&lt;p&gt;The last suggestion is to remove the padding from the
&lt;a href="https://metacpan.org/module/Path::Class" target="_blank"&gt;Path::Class&lt;/a&gt; imports. It&amp;rsquo;s good to be
consistent. The second and third suggestions look to be solid. Let&amp;rsquo;s make this
change.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;perlimports -i --ignore-modules namespace::clean,Test::Most --no-padding lib&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That gives us:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git --no-pager diff lib
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;diff --git a/lib/Acme/Odometer.pm b/lib/Acme/Odometer.pm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;index 7fee773..cb1734e &lt;span class="m"&gt;100644&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--- a/lib/Acme/Odometer.pm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+++ b/lib/Acme/Odometer.pm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -8,10 +8,10 @@ package Acme::Odometer&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; use Moo 1.001&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; use namespace::clean&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use GD&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Memoize&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use GD &lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Memoize qw&lt;span class="o"&gt;(&lt;/span&gt;memoize&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; use MooX::Types::MooseLike::Numeric qw&lt;span class="o"&gt;(&lt;/span&gt;PositiveInt PositiveOrZeroInt&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-use Path::Class qw&lt;span class="o"&gt;(&lt;/span&gt; file &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+use Path::Class qw&lt;span class="o"&gt;(&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s pretty good. Do the tests still pass? Yes, they do. So, we can commit
this change as well. Not every introduction of &lt;code&gt;perlimports&lt;/code&gt; will be this easy,
but it&amp;rsquo;s nice to start off with a win. Can we improve our experience? I think
so. Paring down our use of the command line switches would be a good start. We
can do that via a config file.&lt;/p&gt;
&lt;h2 class="relative group"&gt;A Configuration File
&lt;div id="a-configuration-file" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#a-configuration-file" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Aside from just running &lt;code&gt;perlimports&lt;/code&gt; with fewer switches at the command line,
what if we wanted to run &lt;code&gt;perlimports&lt;/code&gt; via
&lt;a href="https://github.com/bscan/PerlNavigator" target="_blank"&gt;the Perl Navigator Language Server&lt;/a&gt;? It
would be better if we didn&amp;rsquo;t have to worry about the custom command line
switches there as well. This sounds like a good time to create a config file.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;perlimports --create-config-file perlimports.toml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Nice! We have a stub configuration file. Let&amp;rsquo;s see what&amp;rsquo;s inside
&lt;code&gt;perlimports.toml&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Valid log levels are:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# debug, info, notice, warning, error, critical, alert, emergency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# critical, alert and emergency are not currently used.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Please use boolean values in this config file. Negated options (--no-*) are&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# not permitted here. Explicitly set options to true or false.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Some of these values deviate from the regular perlimports defaults. In&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# particular, you&amp;#39;re encouraged to leave preserve_duplicates and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# preserve_unused disabled.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c"&gt;# setting this to true is currently discouraged&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ignore_modules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ignore_modules_filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ignore_modules_pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# regex like &amp;#34;^(Foo|Foo::Bar)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ignore_modules_pattern_filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;libs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;lib&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;t/lib&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log_filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;warn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;never_export_modules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;never_export_modules_filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;preserve_duplicates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;preserve_unused&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tidy_whitespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s commit the stub file to git and then let&amp;rsquo;s move our command line switches
to the config file. The diff should look something like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git --no-pager diff perlimports.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;diff --git a/perlimports.toml b/perlimports.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;index d631998..1e54c9e &lt;span class="m"&gt;100644&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--- a/perlimports.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+++ b/perlimports.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -10,7 +10,7 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# preserve_unused disabled.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# setting this to true is currently discouraged&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-ignore_modules &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+ignore_modules &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;namespace::clean&amp;#34;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;#34;Test::Most&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;ignore_modules_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;ignore_modules_pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# regex like &amp;#34;^(Foo|Foo::Bar)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;ignore_modules_pattern_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -19,7 +19,7 @@ &lt;span class="nv"&gt;log_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;log_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;warn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;never_export_modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;never_export_modules_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-padding &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+padding &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;preserve_duplicates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;preserve_unused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;tidy_whitespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can iterate on this config file as we start tidying more of our code, but
this is already an excellent start. Now we&amp;rsquo;re ready to start setting up editor
integrations.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Neovim
&lt;div id="neovim" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#neovim" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;nvim-lint
&lt;div id="nvim-lint" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#nvim-lint" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Users of Neovim can enable &lt;code&gt;perlimports&lt;/code&gt; linting via
&lt;a href="https://github.com/mfussenegger/nvim-lint" target="_blank"&gt;https://github.com/mfussenegger/nvim-lint&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;Language Server Protocol (LSP)
&lt;div id="language-server-protocol-lsp" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#language-server-protocol-lsp" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;Perl Navigator
&lt;div id="perl-navigator" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#perl-navigator" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re using an editor with LSP support (like Neovim or VS Code), you can
hopefully get &lt;code&gt;perlimports&lt;/code&gt; running via
&lt;a href="https://github.com/bscan/PerlNavigator" target="_blank"&gt;the Perl Navigator Language Server&lt;/a&gt;.
This language server has &lt;code&gt;perlimports&lt;/code&gt; disabled by default, so we&amp;rsquo;ll need to
switch it on in the configuration and also make sure that we have the
&lt;code&gt;perlimports&lt;/code&gt; binary installed and in our &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;perlimportsProfile&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;perlimports.toml&amp;#39;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;perlimportsLintEnabled&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="err"&gt;true,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;perlimportsTidyEnabled&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="err"&gt;true,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In addition to editor configuration, we can now think about adding &lt;code&gt;perlimports&lt;/code&gt;
to our Continuous Integration and &lt;code&gt;pre-commit&lt;/code&gt; hooks as well, so that we
maintain the changes we&amp;rsquo;ve just imposed. We could do this via &lt;code&gt;precious&lt;/code&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Power of precious
&lt;div id="the-power-of-precious" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-power-of-precious" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I talked about using &lt;a href="https://metacpan.org/module/Code::TidyAll" target="_blank"&gt;Code::TidyAll&lt;/a&gt;
in
&lt;a href="https://perladvent.org/2014/2014-12-16.html" target="_blank"&gt;How Santa&amp;rsquo;s Elves Keep their Workshop Tidy&lt;/a&gt;.
&lt;a href="https://metacpan.org/module/tidyall" target="_blank"&gt;tidyall&lt;/a&gt; is a wonderful tool that solves a
lot of problems, but its design was not perfect and it&amp;rsquo;s looking for a new
maintainer. In the meantime,
&lt;a href="https://github.com/houseabsolute/precious" target="_blank"&gt;precious&lt;/a&gt; has drawn inspiration from
&lt;a href="https://metacpan.org/module/tidyall" target="_blank"&gt;tidyall&lt;/a&gt; and can be regarded as its
spiritual successor, even if it&amp;rsquo;s written in Rust. If we want to run
&lt;code&gt;perlimports&lt;/code&gt; along with other fixing and linting tools, we can use &lt;code&gt;precious&lt;/code&gt;
for this.&lt;/p&gt;
&lt;p&gt;We won&amp;rsquo;t cover installation here, but after installing &lt;code&gt;precious&lt;/code&gt; we can
generate a stub config file:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ precious config init --component perl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing precious.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;The generated precious.toml requires the following tools to be installed:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  https://metacpan.org/dist/Perl-Critic
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  https://metacpan.org/dist/Perl-Tidy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  https://metacpan.org/dist/App-perlimports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  https://metacpan.org/dist/Pod-Checker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  https://metacpan.org/dist/Pod-Tidy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at the created file:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;excludes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="s2"&gt;&amp;#34;.build/**&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="s2"&gt;&amp;#34;blib/**&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;perlcritic&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lint&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;**/*.{pl,pm,t,psgi}&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;perlcritic&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--profile=$PRECIOUS_ROOT/perlcriticrc&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ok_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint_failure_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;perltidy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;both&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;**/*.{pl,pm,t,psgi}&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;perltidy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--profile=$PRECIOUS_ROOT/perltidyrc&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--assert-tidy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--no-standard-output&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--outfile=/dev/null&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tidy_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--backup-and-modify-in-place&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--backup-file-extension=/&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ok_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint_failure_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ignore_stderr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Begin Error Output Stream&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;perlimports&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;both&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;**/*.{pl,pm,t,psgi}&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;perlimports&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--lint&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tidy_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-i&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ok_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;expect_stderr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;podchecker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lint&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;**/*.{pl,pm,pod}&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;podchecker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--warnings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--warnings&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ok_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint_failure_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ignore_stderr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="s2"&gt;&amp;#34;.+ pod syntax OK&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="s2"&gt;&amp;#34;.+ does not contain any pod commands&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;podtidy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tidy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;**/*.{pl,pm,pod}&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;podtidy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--columns&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;80&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--inplace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--nobackup&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ok_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint_failure_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can see that the config already includes a linting and tidying configuration
for lots of helpful Perl linters and tidiers, including &lt;code&gt;perlimports&lt;/code&gt;. Now, we
can run &lt;code&gt;precious tidy --all&lt;/code&gt; or &lt;code&gt;precious lint --all&lt;/code&gt; to run all sorts of
checks in &lt;code&gt;pre-commit&lt;/code&gt; hooks and other places where code quality needs to be
ensured.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;precious&lt;/code&gt; is a powerful tool and it merits its own blog post, but let&amp;rsquo;s leave
this as a quick introduction. Perhaps you&amp;rsquo;ll feel inspired to try it out.&lt;/p&gt;
&lt;h2 class="relative group"&gt;That&amp;rsquo;s a Wrap
&lt;div id="thats-a-wrap" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#thats-a-wrap" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I currently have some capacity in my schedule for client work. If you or your
team need help with Perl dependency management and/or integrating &lt;code&gt;perlimports&lt;/code&gt;,
&lt;code&gt;precious&lt;/code&gt; and other code quality tools into your environment,
&lt;a href="https://www.wundersolutions.com" target="_blank"&gt;I&amp;rsquo;m available for hire&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve covered a lot of ground here today. If you have any comments or
suggestions for improvements, please do reach out to me. I&amp;rsquo;d love to hear from
you.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/04/15/getting-started-with-perlimports/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description></item><item><title>GitHub Copilot Chat: First Impressions</title><link>https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/</link><pubDate>Thu, 29 Feb 2024 00:00:00 -0500</pubDate><guid>https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/</guid><description>&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="man with bot"
width="1024"
height="1024"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/featured_hu_c7ef8157f8918cf0.jpeg"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/featured_hu_c7ef8157f8918cf0.jpeg 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Recently I got access to GitHub&amp;rsquo;s Copilot Chat. I had seen a demo of it back in
December and was very impressed with what it can do. Since getting access, I
have been trying to figure out how (and if) Copilot can help to improve my
productivity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota bene&lt;/strong&gt;: This article is not an advertisement for AI. I am aware of many
of the caveats when it comes to AI, but in the short term I am interested in
seeing how I can get a genuine positive result from it.&lt;/p&gt;
&lt;p&gt;Initially I did not think yet another article on AI would have any point. If you
read Hacker News, you may be of the opinion that everyone is using AI already,
but that&amp;rsquo;s not the case. Many smart, competent software developers do not use AI
(yet?). Some of them may not be aware of how they can leverage AI and some may
simply be wary of all the hype.&lt;/p&gt;
&lt;p&gt;My plan is to share some practical examples. This won&amp;rsquo;t be a &amp;ldquo;how to&amp;rdquo; article,
but rather a higher level overview of what might be possible. I won&amp;rsquo;t be going
into details about exactly which prompts I used to get my results. I will leave
that as an exercise for the reader. It is my hope that I&amp;rsquo;ll give you enough
information that you can judge for yourself if it is worth your time to play
around with this.&lt;/p&gt;
&lt;h2 class="relative group"&gt;My Setup
&lt;div id="my-setup" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#my-setup" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I have been using Copilot for completion for a few months now. My setup is
&lt;code&gt;neovim&lt;/code&gt; + &lt;code&gt;nvim-cmp&lt;/code&gt; with the following plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/zbirenbaum/copilot.lua" target="_blank"&gt;zbirenbaum/copilot.lua&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zbirenbaum/copilot-cmp" target="_blank"&gt;zbirenbaum/copilot-cmp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That mostly works for me. Sometimes the completion is a bit funky. Opening up
related files to give Copilot more context is helpful. For me, Copilot is a net
positive, but it can be a bit of a slog sometimes.&lt;/p&gt;
&lt;p&gt;In order to use Copilot Chat, I opted to set up VS Code with the following
plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Copilot&lt;/li&gt;
&lt;li&gt;GitHub Copilot Chat&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means that I need to live in VS Code when using Copilot&amp;rsquo;s Chat features.
This is not ideal, but so far I accept the trade-off as necessary and I am not
grumbling about it. I am still in &lt;code&gt;neovim&lt;/code&gt; when VS Code is not required, but
with all of the file reloading, they are playing nicely together for the most
part.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I should note that
&lt;a href="https://github.com/CopilotC-Nvim/CopilotChat.nvim" target="_blank"&gt;CopilotC-Nvim/CopilotChat.nvim&lt;/a&gt;
does exist. I haven&amp;rsquo;t tried it as I wanted to get up and running in a hurry.
&lt;code&gt;neovim&lt;/code&gt; purists may prefer it.&lt;/em&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;Building a New Jekyll Site
&lt;div id="building-a-new-jekyll-site" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#building-a-new-jekyll-site" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As you may be aware,
&lt;a href="https://www.linkedin.com/in/olafalders" target="_blank"&gt;I am looking to pick up some freelance work&lt;/a&gt;
right now. I have had some success, but there is still room in my schedule. I
have not actively looked for work in some time. My web site did not look very
modern and the listed skill set was dated &amp;ndash; I have not written an iOS app in
years.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/old.jpg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="the legacy site"
width="2822"
height="1424"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/old_hu_f28155ce9ab72a6f.jpg"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/old_hu_f28155ce9ab72a6f.jpg 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/old_hu_5e0a6773562a1cce.jpg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/old.jpg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My site was deployed using a web application framework, but I no longer need to
do anything dynamic. Time to find a static site generator. Jekyll is a very
mature site builder with lots of available themes, so I started there.&lt;/p&gt;
&lt;p&gt;First,
&lt;a href="https://github.com/jeromelachaud/freelancer-theme" target="_blank"&gt;I settled on a theme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-top.jpg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="the theme"
width="2802"
height="1490"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-top_hu_897c5bb9f5a1d894.jpg"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-top_hu_897c5bb9f5a1d894.jpg 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-top_hu_30605e28b6574413.jpg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-top.jpg"&gt;&lt;/figure&gt;
&lt;/a&gt;
&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-portfolio.jpg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="the theme"
width="2832"
height="1490"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-portfolio_hu_b9d613c86944f132.jpg"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-portfolio_hu_b9d613c86944f132.jpg 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-portfolio_hu_7dc2f9d43b92c3f5.jpg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/theme-portfolio.jpg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I like the look of it. It has a lot of GitHub stars and a small number of open
issues and pull requests. I extrapolate wildly from this that people are using
it and not having a great deal of problems. That&amp;rsquo;s good enough for right now.
It&amp;rsquo;s going to be a huge improvement.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Installing rbenv
&lt;div id="installing-rbenv" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#installing-rbenv" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;I needed &lt;code&gt;ruby&lt;/code&gt; to run my Jekyll build locally. I&amp;rsquo;ll want to be able to edit my
site on more than one machine, so I quickly wrote
&lt;a href="https://github.com/oalders/is/blob/main/examples/rbenv.sh" target="_blank"&gt;an installer script&lt;/a&gt;
to install &lt;code&gt;rbenv&lt;/code&gt; if needed and then to install an arbitrary version of &lt;code&gt;ruby&lt;/code&gt;
if needed. I used &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt;, which is a command line
tool which I recently wrote. I am quite happy with how simple and readable this
script turned out to be. If you&amp;rsquo;re curious about &lt;code&gt;is&lt;/code&gt;, I have written
&lt;a href="https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/" &gt;an introduction to is&lt;/a&gt; not
so long ago.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Replacing Google
&lt;div id="replacing-google" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#replacing-google" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;As I was exploring what I needed to do, I used Copilot Chat as a Google
replacement, asking it the kind of things I normally would have searched Google
for. That went very well. I got good information without having to wade through
a top result from W3Schools followed by lots of mediocre Medium posts and dated
StackOverflow answers.&lt;/p&gt;
&lt;p&gt;Using Copilot as my search engine has the added benefit of not requiring me to
leave my editor. Once the response has been generated, being able directly to
replace code with Copilot&amp;rsquo;s suggestion is a nice little productivity feature as
well.&lt;/p&gt;
&lt;p&gt;Aside from not needing Google for good information about how to set up Jekyll, I
have found Copilot to be powerful when doing mundane things like scaffolding
config files. I can say something like &amp;ldquo;Give me a best practice &lt;code&gt;eslint&lt;/code&gt;
configuration&amp;rdquo; and I might see an answer like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;env&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;browser&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;es6&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;extends&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;eslint:recommended&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;plugin:react/recommended&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;globals&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Atomics&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;readonly&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;SharedArrayBuffer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;readonly&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;parserOptions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ecmaFeatures&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;jsx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ecmaVersion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2018&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sourceType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;module&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;react&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;rules&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;indent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;linebreak-style&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;quotes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;double&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;semi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;always&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;no-console&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;warn&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;no-unused-vars&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;warn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;react&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;detect&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can argue about whether or not this really is best practice, but I do not
have strong opinions on this. At the end of the day I just want code that is
consistent and this gives me a starting point, since it&amp;rsquo;s in my nature to fiddle
with settings. Also, VS Code tells me what the config file should be called.
That may seem trivial but on more than one occasion I have struggled with
figuring out why some tool is not working only to realize the file needed to
start with a dot.&lt;/p&gt;
&lt;p&gt;Looking back on this, I could have refined the prompt to say that I&amp;rsquo;m not using
React, which should have pared down the file a bit, but for an 80/20 approach
where I try to get the main things done now and I get to fiddle with it later,
superfluous settings in a config file aren&amp;rsquo;t going to keep me up at night.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Front End Design
&lt;div id="front-end-design" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#front-end-design" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;As I was working on the layout, I asked Copilot for help in tweaking the layout.
It helpfully gave me advice and generated CSS to go with it.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Asset Upgrades
&lt;div id="asset-upgrades" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#asset-upgrades" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;As I took a closer look at the theme, I saw that some included assets were very
old.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bootstrap (3.2.0, released in June 2014)&lt;/li&gt;
&lt;li&gt;jQuery (1.11.0 from January 2014)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything still worked, but this seemed like a fun chance to experiment with
Copilot. Could it do the right thing or would I be stuck in a world of find and
replace? Time to find out. I upgraded the Bootstrap assets and asked Copilot to
upgrade the CSS usage to Bootstrap 5. To my surprise, that mostly just worked.&lt;/p&gt;
&lt;p&gt;As for jQuery, I asked Copilot to convert it to pure JavaScript. That worked
very well, requiring very little intervention from me. I even eliminated a 3rd
party library in the process. jQuery was not bringing much to the table here and
I was able to eliminate a pretty large dependency. So far, so good.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Asset Creation
&lt;div id="asset-creation" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#asset-creation" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;I then used Microsoft Image Creator to come up with some artwork.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-top.jpg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="the theme"
width="2836"
height="1552"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-top_hu_9755e0e8c9089dee.jpg"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-top_hu_9755e0e8c9089dee.jpg 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-top_hu_ba6bc35f1f1611d6.jpg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-top.jpg"&gt;&lt;/figure&gt;
&lt;/a&gt;
&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-services.jpg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="the theme"
width="2834"
height="1552"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-services_hu_831688b00f43170a.jpg"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-services_hu_831688b00f43170a.jpg 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-services_hu_4181f51b95ec60ba.jpg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/new-services.jpg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Again I was quite happy with &lt;a href="https://www.wundersolutions.com" target="_blank"&gt;the result&lt;/a&gt;. The
image creation has some limitations. It seems to want to give me blue eyes, even
when I ask for brown. It also makes my White Shepherd look more like a Husky,
but that feels like quibbling. In real life she often gets called a Husky,
occasionally even by Husky owners.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Responsive Design
&lt;div id="responsive-design" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#responsive-design" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;The only remaining issue was that the layout did not work consistently across
various device resolutions. Copilot could not help me with this, partly because
I lacked the vocabulary to describe the problem. Luckily a very generous friend
gave me the help that I needed to get this little problem sorted out.&lt;/p&gt;
&lt;h3 class="relative group"&gt;The End Result
&lt;div id="the-end-result" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-end-result" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Over a few hours on a Saturday and Sunday, I was able to switch to
&lt;a href="https://www.wundersolutions.com" target="_blank"&gt;a brand new site&lt;/a&gt;, run by a static site
generator. When I push my commits, the Jekyll site rebuilds and publishes via
GitHub pages. It all requires very little intervention from me. As someone who
does a bit of web hosting, it might seem weird that I am not dogfooding my web
site, but at this point in my career I am more interested in the right tools for
the right job. Occasionally I enjoy an overly complicated hobby project, but
this aint it.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Lessons Learned
&lt;div id="lessons-learned" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lessons-learned" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Now that I&amp;rsquo;ve used AI to kick start a rapid web site replacement, what have I
learned?&lt;/p&gt;
&lt;h4 class="relative group"&gt;Trust, but Verify
&lt;div id="trust-but-verify" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#trust-but-verify" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Copilot will tell you things with such confidence that it can be jarring when it
is clearly wrong. I recently gave Copilot a bit of Ansible config which updates
a system configuration file. I asked for this to be converted to an equivalent
bash one-liner. Copilot was more than happy to do this. The caveat was that the
resulting code was set up to blow away the original multi-line system file and
replace it with the one line I was trying to add. I did not get caught by this
bit of misinformation, but &lt;strong&gt;caveat emptor&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t been using Copilot in anger on really gnarly or convoluted code.
Copilot cannot get context from all of the code in a really large project. The
bigger and more complicated your project is, the greater the possibility that
you&amp;rsquo;ll get less than helpful output from Copilot. Just as you would not blindly
copy/paste a StackOverflow answer into production code, you don&amp;rsquo;t want to ship
Copilot suggestions without proper scrutiny.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Copilot Can Tidy Things that Tidiers Possibly Can&amp;rsquo;t
&lt;div id="copilot-can-tidy-things-that-tidiers-possibly-cant" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#copilot-can-tidy-things-that-tidiers-possibly-cant" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;I love tidiers and tidying. I talked about this a bit in
&lt;a href="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/" &gt;my post about typos&lt;/a&gt;. I have long been
frustrated by the lack of tidiers which are template-aware. Copilot did a great
job of respecting Jekyll template syntax when I asked it to reformat the HTML in
the files. I am hopeful it is equally good at Go and Perl HTML templates. Even
if it is currently not, I am confident that this is on the horizon.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Mundane Tasks Become Less Soul-crushing
&lt;div id="mundane-tasks-become-less-soul-crushing" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mundane-tasks-become-less-soul-crushing" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;I recently wrote two Playwright tests in TypeScript which were largely similar,
but with important, subtle differences. Copilot not only refactored these to
allow them to share common code, but it actually created table-driven tests for
me. The result was so much cleaner and more maintainable than what I had started
with. The practical benefit is that I hope I am in a position where I do not
always have to write code with the inevitable refactor in mind. I liken this to
the fact that I rarely format my own code these days. I just write something
that&amp;rsquo;s mostly readable and I rely on a tidier to enforce style.&lt;/p&gt;
&lt;p&gt;The wider implication is that if I am asked, in the parameters of a code review,
to do some tedious task that I really do not want to do because &amp;ldquo;why are you
asking me now when you could have saved me a bunch of time by mentioning this
earlier&amp;rdquo;, I can ask Copilot to implement the request for me. If it works,
everybody wins. If it does not, then I still have the choice to punt on the
change if I think it can wait or to dig in and try to refactor the code myself.&lt;/p&gt;
&lt;p&gt;What that means to me is not having to spend mental energy on things that do not
matter that much. I can lose a lot of time dealing with code review nitpicks
that I often do not have the energy to argue against. Now I have a chance to see
if my new toy can implement the request for me.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Legacy Projects Get New Life
&lt;div id="legacy-projects-get-new-life" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#legacy-projects-get-new-life" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;I recently realized that the landing page for
&lt;a href="https://perladvent.org/" target="_blank"&gt;the Perl Advent Calendar&lt;/a&gt; looked quite dated. If you
looked at it you would be forgiven for thinking the calendar was possibly
abandoned. We published a full calendar of articles in 2023 and 2022, so it&amp;rsquo;s
anything but abandoned.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-old.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="the legacy advent page"
width="2156"
height="738"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-old_hu_d0a4e1352bfb1468.png"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-old_hu_d0a4e1352bfb1468.png 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-old_hu_c35511dd59efe37d.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-old.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/perladvent/Perl-Advent/pull/407" target="_blank"&gt;I asked Copilot&lt;/a&gt; to
modernize the design and it did so quite happily, giving me a workable result on
the first try. I then asked it to add some JavaScript to redirect to a different
page in December and January of any given year. These are all things which I
could have done myself, but I did not want to.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-new.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="the new advent page"
width="1484"
height="738"
src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-new_hu_25109ba708290122.png"
srcset="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-new_hu_25109ba708290122.png 800w, https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-new_hu_92839cea77353371.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/advent-new.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can argue that this kind of work isn&amp;rsquo;t hard. It&amp;rsquo;s not really, but it was
onerous enough that nobody did it for many years. I liken this to taking on a
task myself or having an intern do it. If the intern does it, I still need to
explain the problem and check the work, but it frees me up to do something else
in the meantime. If the intern is effective, it&amp;rsquo;s a time saver. If the intern
needs a lot of help, then I haven&amp;rsquo;t gained much at all. I need to figure out
what the intern can handle and then assign appropriate tasks.&lt;/p&gt;
&lt;p&gt;With refactoring and modernising of code becoming easier to handle, the burden
of maintenance decreases. Perhaps projects which have fallen into disrepair can
more easily be revived.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Creating Tabular Content
&lt;div id="creating-tabular-content" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#creating-tabular-content" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;I love Markdown tables, but boy do I not enjoy creating them. Let&amp;rsquo;s try it with
Copilot.&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;Create a markdown table of the names of the 12 months of the year. Each row
represents one month. The columns are the month names in Latin, Italian,
Spanish, French and German&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Suprisingly, we get what we asked for.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Latin | Italian | Spanish | French | German |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| ---------- | --------- | ---------- | --------- | --------- |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Ianuarius | Gennaio | Enero | Janvier | Januar |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Februarius | Febbraio | Febrero | Février | Februar |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Martius | Marzo | Marzo | Mars | März |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Aprilis | Aprile | Abril | Avril | April |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Maius | Maggio | Mayo | Mai | Mai |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Iunius | Giugno | Junio | Juin | Juni |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Iulius | Luglio | Julio | Juillet | Juli |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Augustus | Agosto | Agosto | Août | August |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| September | Settembre | Septiembre | Septembre | September |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| October | Ottobre | Octubre | Octobre | Oktober |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| November | Novembre | Noviembre | Novembre | November |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| December | Dicembre | Diciembre | Décembre | Dezember |&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Will we double-check the spelling before using this in anger? You bet we will,
but if we had typed this by hand we would have checked it anyway.&lt;/p&gt;
&lt;p&gt;If we want this table in HTML, we say &amp;ldquo;convert the table to HTML&amp;rdquo; and it is
done. Likewise, we can ask for a CSV:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csv" data-lang="csv"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Latin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Italian&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Spanish&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;French&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;German&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Ianuarius&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Gennaio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Enero&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Janvier&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Januar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Februarius&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Febbraio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Febrero&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Février&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Februar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Martius&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Marzo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Marzo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Mars&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;März&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Aprilis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Aprile&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Abril&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Avril&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;April&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Maius&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Maggio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Mayo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Mai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Mai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Iunius&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Giugno&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Junio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Juin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Juni&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Iulius&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Luglio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Julio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Juillet&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Juli&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Augustus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Agosto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Agosto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Août&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;August&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;September&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Settembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Septiembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Septembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;September&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;October&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Ottobre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Octubre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Octobre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Oktober&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;November&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Novembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Noviembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Novembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;November&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;December&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Dicembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Diciembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Décembre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Dezember&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Of course you can already do this with bash/sed/awk/jq or what have you, but not
everyone views command line data transformation as a rewarding hobby. Now you
have the option of getting arbitrary tabular data just by asking for it. It&amp;rsquo;s
one more tool in your toolbox.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Code Review
&lt;div id="code-review" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#code-review" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Looking to the future, I see how Copilot could be useful when it comes to code
reviews.&lt;/p&gt;
&lt;h5 class="relative group"&gt;Ask Copilot to Explain Code
&lt;div id="ask-copilot-to-explain-code" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ask-copilot-to-explain-code" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h5&gt;
&lt;p&gt;If I check out the branch I am reviewing and select some code with a language
feature I do not quite understand, I can ask Copilot to explain it to me. (If I
am asking it to explain some convoluted code, that&amp;rsquo;s a red flag for the review,
but it can still help me get from A to B). I see this as potentially a
significant time saver, but not a replacement for an in depth review by a human.&lt;/p&gt;
&lt;p&gt;The same pattern can be applied to SQL. If you&amp;rsquo;re dealing with a lot of nested
subqueries, CTEs (Common Table Expressions) or someone who treats SQL as their
favourite programming language, then Copilot can help you untangle the logic.
It&amp;rsquo;s no replacement for trying to understand the code yourself and it&amp;rsquo;s not
always going to be 100% correct, but it&amp;rsquo;s nice as a second opinion. I expect
this functionality will also improve with time.&lt;/p&gt;
&lt;h5 class="relative group"&gt;Ask Copilot to Suggest Improvements
&lt;div id="ask-copilot-to-suggest-improvements" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ask-copilot-to-suggest-improvements" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h5&gt;
&lt;p&gt;On a similar note, I can select some code and ask Copilot to suggest
improvements. This may bring up some points which I had not previously
considered. Again, it is not a replacement for the human review, but I imagine
this could find issues which may get glossed over otherwise, based on who is
reviewing the code.&lt;/p&gt;
&lt;p&gt;Taking this one step further, I can ask Copilot for code suggestions before
submitting my own code for review. This can catch issues before we start the
code review feedback loop. The more I can prevent a lot of back and forth in a
code review, the better placed I am to get it merged quickly. So this could be a
time saver as well.&lt;/p&gt;
&lt;h4 class="relative group"&gt;Improve Copy
&lt;div id="improve-copy" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#improve-copy" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Copilot can suggest improvements to documentation or even plain web site copy.
When I tried this it, the suggestions were a clear improvement, turning awkward
turns of phrase into something more easily readable. This can be useful in the
proofreading phase, especially for non-native English speakers. (I have no idea
how it performs in other languages.) I do often come across technical blog posts
where the author&amp;rsquo;s language skills get in the way of the point they are trying
to make. This could be a very useful tool in that case.&lt;/p&gt;
&lt;h3 class="relative group"&gt;That&amp;rsquo;s a Wrap
&lt;div id="thats-a-wrap" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#thats-a-wrap" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;There is a lot of concern about the explosion of AI and I think that these
discussions are generally good to have, even if I am not going to get into that
here. As far some of my other successful experiments, there are sill a number I
did not even get to today. One of my most positive experiences has been with
using AI to sell items on Kijiji, but there is too much to explore there. Maybe
that&amp;rsquo;s a subject for another day.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve made it this far, thanks for sticking with me on this meandering
journey &amp;ndash; it has been an enjoyable ride.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2024/02/29/github-Copilot-chat-first-impressions/featured.jpeg"/></item><item><title>Find and Fix More Typos</title><link>https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/</link><pubDate>Tue, 06 Feb 2024 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/</guid><description>&lt;p&gt;&lt;em&gt;This is a revised and expanded version of a post which originally appeared in
&lt;a href="https://perladvent.org/2023/2023-12-21.html" target="_blank"&gt;the 2023 Perl Advent Calendar&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="800"
height="600"
src="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/featured.jpeg"
srcset="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/featured.jpeg 800w, https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/99785739@N00/4285676390"&gt;Typewriter Eraser, Scale X&lt;/a&gt;" by &lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/99785739@N00"&gt;ChrisYunker&lt;/a&gt; is licensed under &lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC BY 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;re constantly producing code and documentation, mistakes will happen.
Some will be easy to spot and others may elude you. We know that attention to
detail counts. When someone finds a typo in your documentation or your code,
they may be inclined to ask themselves what else may be wrong. In order to avoid
the impression of carelessness, let&amp;rsquo;s automate the finding and fixing of typos.&lt;/p&gt;
&lt;h3 class="relative group"&gt;The Tool
&lt;div id="the-tool" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-tool" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;One of the tools which we have in our toolkit is the
&lt;a href="https://crates.io/crates/typos" target="_blank"&gt;typos&lt;/a&gt; utility. The wonderful thing about
&lt;code&gt;typos&lt;/code&gt; is that this Rust-based tool can improve code and documentation in many
different languages. It&amp;rsquo;s that good!&lt;/p&gt;
&lt;h3 class="relative group"&gt;How it Works
&lt;div id="how-it-works" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-works" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;From the docs:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;typos maintains a list of known typo corrections to keep the false positive
count low so it can safely run unassisted.&lt;/p&gt;
&lt;p&gt;This is in contrast to most spell checking UIs people use where there is a
known list of valid words. In this case, the spell checker tries to guess your
intent by finding the closest-looking word. It then has a gauge for when a
word isn&amp;rsquo;t close enough and assumes you know best. The user has the
opportunity to verify these corrections and explicitly allow or reject them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 class="relative group"&gt;Getting Set Up
&lt;div id="getting-set-up" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#getting-set-up" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;After installing &lt;code&gt;typos&lt;/code&gt;, it&amp;rsquo;s trivial to spot typos in code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ typos
[some output appears]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;rsquo;s it! Now, let&amp;rsquo;s try it on the Perl Advent Calendar. We&amp;rsquo;ll start with the
articles in 2019. This gives us all sorts of helpful information.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ typos &lt;span class="m"&gt;2019&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error: &lt;span class="sb"&gt;`&lt;/span&gt;entires&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;entries&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; 2019/articles/2019-12-16.pod:253:23
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;253&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; And start adding todo entires:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; ^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error: &lt;span class="sb"&gt;`&lt;/span&gt;hightlight&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;highlight&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; 2019/articles/2019-12-14.pod:113:56
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;113&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# work out the three strings (left of / before hightlight,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; ^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error: &lt;span class="sb"&gt;`&lt;/span&gt;hightlight&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;highlight&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; 2019/articles/2019-12-14.pod:114:24
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;114&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# the middle / hightlight, and then right of / after highlight)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; ^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error: &lt;span class="sb"&gt;`&lt;/span&gt;assing&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;assign&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; 2019/articles/2019-12-14.pod:121:27
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;121&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# what we want to assing to $x so we start drawing there again.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There are actually a few more typos, but this is a good demonstration of what we
might find. Now, how do we fix it?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ typos -w &lt;span class="m"&gt;2019&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error: &lt;span class="sb"&gt;`&lt;/span&gt;fils&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;fills&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;files&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;file&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; 2019/articles/2019-12-20.pod:136:59
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;136&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; that machine can access the internet, &lt;span class="k"&gt;if&lt;/span&gt; I can put the fils in my home
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; ^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error: &lt;span class="sb"&gt;`&lt;/span&gt;whe&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;when&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;we&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; 2019/articles/2019-12-19.pod:25:17
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;25&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; between runs.&lt;span class="s2"&gt;&amp;#34;, whe Wise Old Elf explained sagely, &amp;#34;&lt;/span&gt;Then you&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;d be able to
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; ^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This actually fixes a number of typos, but there are two which the tool cannot
resolve, so it makes some suggestions for us to use in a manual fix.&lt;/p&gt;
&lt;p&gt;This leaves the matter of how many changes were actually made. Let&amp;rsquo;s see:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git diff --stat &lt;span class="m"&gt;2019&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2010-12-11.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; +-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-04.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; +-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-05.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; ++--
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-07.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; ++--
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-10.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; +-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-12.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; +-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-13.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; ++++----
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-14.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt; +++---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-16.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; +-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-18.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt; +++---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-20.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; ++--
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-21.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; ++--
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-22.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; +-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2019/articles/2019-12-24.pod &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; +-
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;14&lt;/span&gt; files changed, &lt;span class="m"&gt;25&lt;/span&gt; insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="m"&gt;25&lt;/span&gt; deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can now go over the changes individually via &lt;code&gt;git add -p&lt;/code&gt; and we&amp;rsquo;ll find that
all but one seem to be correct. 24 typos found and fixed in 47 ms. That&amp;rsquo;s not
bad at all.&lt;/p&gt;
&lt;p&gt;As we process more and more files, we will likely find some false positives
which we want to correct. This can be done in a config file. Ours is
&lt;code&gt;typos.toml&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend-words&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ANDed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ANDed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ba&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ba&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Claus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Claus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;DNE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DNE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;IFF&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;IFF&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Signes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Signes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;extend-exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;advent.ini&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;data.en&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In order to skip false positives, we add words to the &lt;code&gt;default.extend-words&lt;/code&gt;
section. Yes, it&amp;rsquo;s kind of a surprising way to do this, but let&amp;rsquo;s go along with
it. Also, any files which we don&amp;rsquo;t want to check can be added to
&lt;code&gt;extend-include&lt;/code&gt; in the &lt;code&gt;[files]&lt;/code&gt; section.&lt;/p&gt;
&lt;h3 class="relative group"&gt;More Than Just Documentation
&lt;div id="more-than-just-documentation" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#more-than-just-documentation" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt; typos 2019
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The neat thing about &lt;code&gt;typos&lt;/code&gt; is that it can also fix things in your code itself
and not just the documentation.&lt;/p&gt;
&lt;p&gt;Imagine we have a Perl script with the following contents:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-perl" data-lang="perl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;sub&lt;/span&gt; &lt;span class="nf"&gt;widht&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$strng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;What does &lt;code&gt;typos&lt;/code&gt; have to say about it?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ typos workshop.pl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error: &lt;span class="sb"&gt;`&lt;/span&gt;widht&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;width&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; workshop.pl:4:5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sub widht &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; ^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error: &lt;span class="sb"&gt;`&lt;/span&gt;strng&lt;span class="sb"&gt;`&lt;/span&gt; should be &lt;span class="sb"&gt;`&lt;/span&gt;string&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; workshop.pl:6:5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;6&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; my &lt;span class="nv"&gt;$strng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; ^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can see that our function name and our variable name have been caught and
will be corrected if we run again with &lt;code&gt;typos -w&lt;/code&gt;.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Digging Deeper
&lt;div id="digging-deeper" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#digging-deeper" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Beyond the basics of &lt;code&gt;typos&lt;/code&gt; and &lt;code&gt;typos -w&lt;/code&gt;, we can do more. Let&amp;rsquo;s touch on a
few things now, but you can always check &lt;code&gt;typos --help&lt;/code&gt; for a more complete list
of options.&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;code&gt;--words&lt;/code&gt;
&lt;div id="--words" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#--words" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;typos&lt;/code&gt; does its best to find words not only in your documentation, but also in
your code. If you&amp;rsquo;re scratching your head as to why a certain word is not being
flagged, you can use the &lt;code&gt;--words&lt;/code&gt; flag to see a list of words in your project
which are being checked. Maybe it&amp;rsquo;s not being discovered?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;typos --words&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To get a more readable list: &lt;code&gt;typos --words | sort | uniq&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To do some fuzzy matching on the list, pipe the results to &lt;code&gt;fzf&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;typos --words &lt;span class="p"&gt;|&lt;/span&gt; sort &lt;span class="p"&gt;|&lt;/span&gt; uniq &lt;span class="p"&gt;|&lt;/span&gt; fzf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;If you&amp;rsquo;re not familiar with &lt;code&gt;fzf&lt;/code&gt;, you may find
&lt;a href="https://www.olafalders.com/categories/fzf/" &gt;some of my fzf articles&lt;/a&gt; to be helpful.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 class="relative group"&gt;&lt;code&gt;--locale&lt;/code&gt;
&lt;div id="--locale" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#--locale" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Are you tired of &lt;code&gt;typos&lt;/code&gt; telling you that the Queen&amp;rsquo;s (King&amp;rsquo;s?) English is
wrong? You can change the locale to &lt;code&gt;en-gb&lt;/code&gt; and you can once again feel at one
with the universe.&lt;/p&gt;
&lt;p&gt;The currently available locales are: &lt;code&gt;en&lt;/code&gt;, &lt;code&gt;en-au&lt;/code&gt;, &lt;code&gt;en-ca&lt;/code&gt;, &lt;code&gt;en-gb&lt;/code&gt; and
&lt;code&gt;en-us&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;typos --locale en-gb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 class="relative group"&gt;&lt;code&gt;--diff&lt;/code&gt;
&lt;div id="--diff" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#--diff" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Would you like to see a diff before you run &lt;code&gt;typos -w&lt;/code&gt;? You can do that via&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;typos --diff&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Going back to our previous example:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;typos --diff foo.pl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- foo.pl original
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ foo.pl fixed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -3,2 +3,2 @@
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-sub widht {}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-my $strng = &amp;#39;foo&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+sub width {}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+my $string = &amp;#39;foo&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 class="relative group"&gt;Integrations
&lt;div id="integrations" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#integrations" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;h4 class="relative group"&gt;Neovim
&lt;div id="neovim" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#neovim" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;Users of Neovim can also enable &lt;code&gt;typos&lt;/code&gt; linting via
&lt;a href="https://github.com/mfussenegger/nvim-lint" target="_blank"&gt;https://github.com/mfussenegger/nvim-lint&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/nvim-lint.png" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="nvim-lint"
width="1454"
height="420"
src="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/nvim-lint_hu_b95e5d7e32a72f83.png"
srcset="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/nvim-lint_hu_b95e5d7e32a72f83.png 800w, https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/nvim-lint_hu_fa0be28b087ca52.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/nvim-lint.png"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h4 class="relative group"&gt;VS Code
&lt;div id="vs-code" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#vs-code" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;There is also a
&lt;a href="https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode" target="_blank"&gt;VS Code extension&lt;/a&gt;
for &lt;code&gt;typos&lt;/code&gt;. I&amp;rsquo;m not in VS Code very often, so I haven&amp;rsquo;t tested this myself, but
that looks to be worth exploring.&lt;/p&gt;
&lt;h4 class="relative group"&gt;LSP
&lt;div id="lsp" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lsp" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;If you prefer the Language Server Protocol (LSP), there is also
&lt;a href="https://github.com/tekumara/typos-vscode/releases" target="_blank"&gt;an LSP integration&lt;/a&gt;, which
is part of the VS Code extension above.&lt;/p&gt;
&lt;h4 class="relative group"&gt;precious
&lt;div id="precious" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#precious" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h4&gt;
&lt;p&gt;If you use &lt;a href="https://github.com/houseabsolute/precious/" target="_blank"&gt;precious&lt;/a&gt; as a harness
around your linters and tidiers, you can start by adding this to your
configuration.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;both&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;**/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;typos&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tidy_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ok_exit_codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you haven&amp;rsquo;t tried &lt;code&gt;precious&lt;/code&gt;, I highly recommend it. I&amp;rsquo;ll be digging a bit
deeper into this tool in a forthcoming post. You may subscribe to email updates
using the form at the bottom of this post, if you don&amp;rsquo;t want to miss it.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Now We Look Smart(er)
&lt;div id="now-we-look-smarter" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#now-we-look-smarter" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Having discovered this very useful tool, we can add it to our linters and
fixers, make it part of our &lt;code&gt;pre-commit&lt;/code&gt; hooks and include it in our Continuous
Integration configuration.&lt;/p&gt;
&lt;p&gt;For those of us who enjoy creating a quick pull request to fix someone else&amp;rsquo;s
typos, we can now employ this tool on repositories which we have just checked
out. A helpful pull request for a grateful maintainer 🙏 is just minutes away.&lt;/p&gt;
&lt;p&gt;As one more data point, I used &lt;code&gt;typos&lt;/code&gt; to check this very article and I&amp;rsquo;m both
proud and ashamed to say that it found more than one problem. 😅&lt;/p&gt;
&lt;p&gt;&lt;code&gt;typos&lt;/code&gt; will not find all errors, but it will find issues which are hard for the
human eye to spot. It&amp;rsquo;s not a replacement for a code review, but it&amp;rsquo;s a helpful
tool to have in your toolbox for the days when you just don&amp;rsquo;t see the typos
which are staring you in the face. The best part is, you get to find the typos
before someone else does. 🥳&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2024/02/06/find-and-fix-more-typos/featured.jpeg"/></item><item><title>Using Tig to Make Sense of Git</title><link>https://www.olafalders.com/2024/01/17/tig-git-browser/</link><pubDate>Wed, 17 Jan 2024 00:00:00 -0500</pubDate><guid>https://www.olafalders.com/2024/01/17/tig-git-browser/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;em&gt;This article was originally published at
&lt;a href="https://opensource.com/article/19/6/what-tig" target="_blank"&gt;opensource.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you work with Git as your version control system, you&amp;rsquo;ve likely already
resigned yourself to the fact that Git is a complicated beast. Git is a
fantastic tool, but it can be cumbersome to navigate repositories, logs, the
stash, etc. That&amp;rsquo;s where a tool like &lt;a href="https://jonas.github.io/tig/" target="_blank"&gt;Tig&lt;/a&gt; comes
in.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="featured"
width="768"
height="768"
src="https://www.olafalders.com/2024/01/17/tig-git-browser/featured.png"
srcset="https://www.olafalders.com/2024/01/17/tig-git-browser/featured.png 800w, https://www.olafalders.com/2024/01/17/tig-git-browser/featured.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/01/17/tig-git-browser/featured.png"&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a target="_blank" rel="noopener noreferrer" href="https://commons.wikimedia.org/w/index.php?curid=50551426"&gt;Git icon&lt;/a&gt;" by &lt;a target="_blank" rel="noopener noreferrer" href="https://twitter.com/jasonlong"&gt;Jason Long&lt;/a&gt; is licensed under &lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/3.0/?ref=openverse"&gt;CC BY 3.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Related posts: I&amp;rsquo;ve also discussed
&lt;a href="https://www.olafalders.com/2024/01/09/git-context-switching/" &gt;Four Strategies for Context Switching in Git&lt;/a&gt;
and &lt;a href="https://www.olafalders.com/2023/04/23/autocorrecting-my-git-commands/" &gt;Autocorrecting Git Commands&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;From the
&lt;a href="http://manpages.ubuntu.com/manpages/bionic/man1/tig.1.html" target="_blank"&gt;Tig man page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;Tig is an ncurses-based text-mode interface for git(1). It functions mainly as
a Git repository browser, but can also assist in staging changes for commit at
chunk level and act as a pager for output from various Git commands.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;This basically means that Tig provides a text-based user interface you can run
in your terminal. Tig makes it easy to browse your Git logs, but it can do much
more than just bounce you around from your last commit to a previous one.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/01/17/tig-git-browser/1-default.png" title="default tig"&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="default tig"
width="2842"
height="1476"
src="https://www.olafalders.com/2024/01/17/tig-git-browser/1-default_hu_b2be458ec4129a55.png"
srcset="https://www.olafalders.com/2024/01/17/tig-git-browser/1-default_hu_b2be458ec4129a55.png 800w, https://www.olafalders.com/2024/01/17/tig-git-browser/1-default_hu_4c097c5b946eef27.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/01/17/tig-git-browser/1-default.png"&gt;&lt;figcaption&gt;default tig&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Many of the examples in this quick introduction to Tig have been poached
directly from its excellent man page. I highly recommend reading it to learn
more.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Install Tig
&lt;div id="install-tig" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#install-tig" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Fedora and RHEL: &lt;strong&gt;sudo dnf install tig&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ubuntu and Debian: &lt;strong&gt;sudo apt install tig&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;MacOS: &lt;strong&gt;brew install tig&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the official
&lt;a href="https://jonas.github.io/tig/INSTALL.html" target="_blank"&gt;installation instructions&lt;/a&gt; for even
more options.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Browse commits in your current branch
&lt;div id="browse-commits-in-your-current-branch" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#browse-commits-in-your-current-branch" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If you want to browse the latest commits in your branch, enter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tig
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;rsquo;s it. This three-character command will launch a browser where you can
navigate the commits in your current branch. You can think of it as a wrapper
around &lt;strong&gt;git log&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To navigate the output, you can use the Up and Down arrow keys to move from one
commit to another. Pressing the Return/Enter key will open a vertical split with
the contents of the chosen commit on the right-hand side. You can continue to
browse up and down in your commit history on the left-hand side, and your
changes will appear on the right. Use &lt;strong&gt;k&lt;/strong&gt; and &lt;strong&gt;j&lt;/strong&gt; to navigate up and down by
line and &lt;strong&gt;-&lt;/strong&gt; and the Space Bar to page up and down on the right-hand side.
Use &lt;strong&gt;q&lt;/strong&gt; to exit the right-hand pane.&lt;/p&gt;
&lt;p&gt;Searching on &lt;strong&gt;tig&lt;/strong&gt; output is simple as well. Use &lt;strong&gt;/&lt;/strong&gt; to search forward and
&lt;strong&gt;?&lt;/strong&gt; to search backward on both the left and right panes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/01/17/tig-git-browser/2-commit.png" title="commit tig"&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="commit tig"
width="2842"
height="1476"
src="https://www.olafalders.com/2024/01/17/tig-git-browser/2-commit_hu_c63c13ba6596131a.png"
srcset="https://www.olafalders.com/2024/01/17/tig-git-browser/2-commit_hu_c63c13ba6596131a.png 800w, https://www.olafalders.com/2024/01/17/tig-git-browser/2-commit_hu_1ac2e928186d6984.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/01/17/tig-git-browser/2-commit.png"&gt;&lt;figcaption&gt;commit tig&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s enough to get you started navigating your commits. There are too many key
bindings to cover here, but clicking &lt;strong&gt;h&lt;/strong&gt; will display a Help menu where you
can discover its navigation and command options. You can also use &lt;strong&gt;/&lt;/strong&gt; and
&lt;strong&gt;?&lt;/strong&gt; to search the Help menu. Use &lt;strong&gt;q&lt;/strong&gt; to exit Help.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/01/17/tig-git-browser/3-help.png" title="help tig"&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="help tig"
width="2842"
height="1476"
src="https://www.olafalders.com/2024/01/17/tig-git-browser/3-help_hu_4d502a10dafe2711.png"
srcset="https://www.olafalders.com/2024/01/17/tig-git-browser/3-help_hu_4d502a10dafe2711.png 800w, https://www.olafalders.com/2024/01/17/tig-git-browser/3-help_hu_38d4cb46ab513811.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/01/17/tig-git-browser/3-help.png"&gt;&lt;figcaption&gt;help tig&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;Browse revisions for a single file
&lt;div id="browse-revisions-for-a-single-file" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#browse-revisions-for-a-single-file" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Since Tig is a wrapper around &lt;strong&gt;git log&lt;/strong&gt;, it conveniently accepts the same
arguments that can be passed to &lt;strong&gt;git log&lt;/strong&gt;. For instance, to browse the commit
history for a single file, enter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tig README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compare this with the output of the Git command being wrapped to get a clearer
view of how Tig enhances the output.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git log README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To include the patches in the raw Git output, you can add a &lt;strong&gt;-p&lt;/strong&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git log -p README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to narrow the commits down to a specific date range, try something
like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tig --after&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2017-01-01&amp;#34;&lt;/span&gt; --before&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2018-05-16&amp;#34;&lt;/span&gt; -- README.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Again, you can compare this with the raw Git version:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git log --after&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2017-01-01&amp;#34;&lt;/span&gt; --before&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2018-05-16&amp;#34;&lt;/span&gt; -- README.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;Browse who changed a file
&lt;div id="browse-who-changed-a-file" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#browse-who-changed-a-file" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Sometimes you want to find out who made a change to a file and why. The command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tig blame README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is essentially a wrapper around &lt;strong&gt;git blame&lt;/strong&gt;. As you would expect, it allows
you to see who the last person was to edit a given line, and it also allows you
to navigate to the commit that introduced the line. This is somewhat like the
&lt;strong&gt;:Gblame&lt;/strong&gt; command Vim&amp;rsquo;s &lt;strong&gt;vim-fugitive&lt;/strong&gt; plugin provides.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Browse your stash
&lt;div id="browse-your-stash" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#browse-your-stash" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re like me, you may have a pile of edits in your stash. It&amp;rsquo;s easy to lose
track of them. You can view the latest item in your stash via:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git stash show -p stash@{0}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find the second most recent item via:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git stash show -p stash@{1}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and so on. If you can recall these commands whenever you need them, you have a
much sharper memory than I do.&lt;/p&gt;
&lt;p&gt;As with the Git commands above, Tig makes it easy to enhance your Git output
with a simple invocation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tig stash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try issuing this command in a repository with a populated stash. You&amp;rsquo;ll be able
to browse &lt;strong&gt;and search&lt;/strong&gt; your stash items, giving you a quick overview of
everything you saved for a rainy day.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Browse your refs
&lt;div id="browse-your-refs" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#browse-your-refs" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;A Git ref is the hash of something you have committed. This includes files as
well as branches. Using the &lt;strong&gt;tig refs&lt;/strong&gt; command allows you to browse all of
your refs and drill down to specific commits.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tig refs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you&amp;rsquo;re finished, use &lt;strong&gt;q&lt;/strong&gt; to return to a previous menu.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Browse git status
&lt;div id="browse-git-status" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#browse-git-status" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If you want to view which files have been staged and which are untracked, use
&lt;strong&gt;tig status&lt;/strong&gt;, a wrapper around &lt;strong&gt;git status&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/01/17/tig-git-browser/4-status.png" title="status tig"&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="status tig"
width="2842"
height="1476"
src="https://www.olafalders.com/2024/01/17/tig-git-browser/4-status_hu_377604e1037aec5c.png"
srcset="https://www.olafalders.com/2024/01/17/tig-git-browser/4-status_hu_377604e1037aec5c.png 800w, https://www.olafalders.com/2024/01/17/tig-git-browser/4-status_hu_58d0866311a1e3da.png 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/01/17/tig-git-browser/4-status.png"&gt;&lt;figcaption&gt;status tig&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;Browse git grep
&lt;div id="browse-git-grep" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#browse-git-grep" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;You can use the &lt;strong&gt;grep&lt;/strong&gt; command to search for expressions in text files. The
command &lt;strong&gt;tig grep&lt;/strong&gt; allows you to navigate the output of &lt;strong&gt;git grep&lt;/strong&gt;. For
example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tig grep -i foo lib/Bar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will navigate the output of a case-insensitive search for &lt;strong&gt;foo&lt;/strong&gt; in the
&lt;strong&gt;lib/Bar&lt;/strong&gt; directory.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Pipe output to Tig via STDIN
&lt;div id="pipe-output-to-tig-via-stdin" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#pipe-output-to-tig-via-stdin" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If you are piping a list of commit IDs to Tig, you must use the &lt;strong&gt;--stdin&lt;/strong&gt;
flag so that &lt;strong&gt;tig show&lt;/strong&gt; reads from stdin. Otherwise, &lt;strong&gt;tig show&lt;/strong&gt; launches
without input (rendering an empty screen).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git rev-list --author=olaf HEAD | tig show --stdin
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 class="relative group"&gt;Add custom bindings
&lt;div id="add-custom-bindings" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#add-custom-bindings" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;You can customize Tig with an &lt;a href="https://en.wikipedia.org/wiki/Run_commands" target="_blank"&gt;rc&lt;/a&gt;
file. Here&amp;rsquo;s how you can configure Tig to your liking, using the example of
adding some helpful custom key bindings.&lt;/p&gt;
&lt;p&gt;Create a file in your home directory called &lt;strong&gt;.tigrc&lt;/strong&gt;. Open &lt;strong&gt;~/.tigrc&lt;/strong&gt; in
your favorite editor and add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Apply the selected stash
bind stash a !?git stash apply %(stash)
# Drop the selected stash item
bind stash x !?git stash drop %(stash)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run &lt;strong&gt;tig stash&lt;/strong&gt; to browse your stash, as above. However, with these bindings
in place, you can press &lt;strong&gt;a&lt;/strong&gt; to apply an item from the stash to your repository
and &lt;strong&gt;x&lt;/strong&gt; to drop an item from the stash. Keep in mind that you&amp;rsquo;ll need to
perform these commands when browsing the stash &lt;strong&gt;list&lt;/strong&gt;. If you&amp;rsquo;re browsing a
stash &lt;strong&gt;item&lt;/strong&gt;, enter &lt;strong&gt;q&lt;/strong&gt; to exit that view and press &lt;strong&gt;a&lt;/strong&gt; or &lt;strong&gt;x&lt;/strong&gt; to get
the effect you want.&lt;/p&gt;
&lt;p&gt;For more information, you can read more about
&lt;a href="https://github.com/jonas/tig/wiki/Bindings" target="_blank"&gt;Tig key bindings&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Wrapping up
&lt;div id="wrapping-up" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#wrapping-up" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I hope this has been a helpful demonstration of how Tig can enhance your daily
workflow. Tig can do even more powerful things (such as staging lines of code),
but that&amp;rsquo;s outside the scope of this introductory article. There&amp;rsquo;s enough
information here to make you dangerous, but there&amp;rsquo;s still more to explore.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/01/17/tig-git-browser/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2024/01/17/tig-git-browser/featured.png"/></item><item><title>4 Strategies for Context Switching in Git</title><link>https://www.olafalders.com/2024/01/09/git-context-switching/</link><pubDate>Tue, 09 Jan 2024 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2024/01/09/git-context-switching/</guid><description>&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/01/09/git-context-switching/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="auto"
alt="switch"
width="799"
height="605"
src="https://www.olafalders.com/2024/01/09/git-context-switching/featured.jpeg"
srcset="https://www.olafalders.com/2024/01/09/git-context-switching/featured.jpeg 800w, https://www.olafalders.com/2024/01/09/git-context-switching/featured.jpeg 1280w"
sizes="(min-width: 768px) 50vw, 65vw"
data-zoom-src="https://www.olafalders.com/2024/01/09/git-context-switching/featured.jpeg"&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;!-- markdownlint-disable-line --&gt;&lt;p class="attribution"&gt;"&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/27076997@N00/3881761860"&gt;Control Panel switch module&lt;/a&gt;" by &lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/27076997@N00"&gt;LogicalZero&lt;/a&gt; is licensed under &lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-sa/2.0/?ref=openverse"&gt;CC BY-SA 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;This post was originally published at
&lt;a href="https://opensource.com/article/21/4/context-switching-git" target="_blank"&gt;opensource.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Anyone who spends a lot of time working with Git will eventually need to do some
form of context switching. Sometimes this adds very little overhead to your
workflow, but other times, it can be a real pain.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Related posts: I&amp;rsquo;ve also discussed
&lt;a href="https://www.olafalders.com/2023/04/23/autocorrecting-my-git-commands/" &gt;Autocorrecting Git Commands&lt;/a&gt; and
&lt;a href="https://www.olafalders.com/2024/01/17/tig-git-browser/" &gt;Using Tig to Make Sense of Git&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s discuss the pros and cons of some common strategies for dealing with
context switching using this example problem:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;Imagine you are working in a branch called &lt;code&gt;feature-X&lt;/code&gt;. You have just
discovered you need to solve an unrelated problem. This cannot be done in
&lt;code&gt;feature-X&lt;/code&gt;. You will need to do this work in a new branch, &lt;code&gt;feature-Y&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;Solution #1: stash + branch
&lt;div id="solution-1-stash--branch" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-1-stash--branch" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Probably the most common workflow to tackle this issue looks something like
this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Halt work on the branch &lt;code&gt;feature-X&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git stash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git switch -c feature-Y origin/main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Hack, hack, hack…&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git switch feature-X&lt;/code&gt; or &lt;code&gt;git switch -&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git stash pop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Resume work on &lt;code&gt;feature-X&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; The nice thing about this approach is that this is a fairly easy
workflow for simple changes. It can work quite well, especially for small
repositories.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; When using this workflow, you can have only one workspace at a time.
Also, depending on the state of your repository, working with the stash can be
non-trivial.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Solution #2: WIP commit + branch
&lt;div id="solution-2-wip-commit--branch" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-2-wip-commit--branch" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;A variation on this solution looks quite similar, but it uses a WIP (Work in
Progress) commit rather than the stash. When you&amp;rsquo;re ready to switch back, rather
than popping the stash, &lt;code&gt;git reset HEAD~1&lt;/code&gt; unrolls your WIP commit, and you&amp;rsquo;re
free to continue, much as you did in the earlier scenario but without touching
the stash.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Halt work on the branch &lt;code&gt;feature-X&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git add -u&lt;/code&gt; (adds only modified and deleted files)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -m &amp;quot;WIP&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git switch -c feature-Y origin/main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Hack, hack, hack…&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git switch feature-X&lt;/code&gt; or &lt;code&gt;git switch -&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git reset HEAD~1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; This is an easy workflow for simple changes and also good for small
repositories. You don&amp;rsquo;t have to work with the stash.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; You can have only one workspace at any time. Also, WIP commits can
sneak into your final product if you or your code reviewer are not vigilant.&lt;/p&gt;
&lt;p&gt;When using this workflow, you &lt;em&gt;never&lt;/em&gt; want to add a &lt;code&gt;--hard&lt;/code&gt; to &lt;code&gt;git reset&lt;/code&gt;. If
you do this accidentally, you should be able to restore your commit using
&lt;code&gt;git reflog&lt;/code&gt;, but it&amp;rsquo;s less heartstopping to avoid this scenario entirely.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Solution #3: new repository clone
&lt;div id="solution-3-new-repository-clone" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-3-new-repository-clone" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;In this solution, rather than creating a new branch, you make a new clone of the
repository for each new feature branch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; You can work in multiple workspaces simultaneously. You don&amp;rsquo;t need
&lt;code&gt;git stash&lt;/code&gt; or even WIP commits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Depending on the size of your repository, this can use a lot of disk
space. (Shallow clones can help with this scenario, but they may not always be a
good fit.) Additionally, your repository clones will be agnostic about each
other. Since they can&amp;rsquo;t track each other, you must track where your clones live.
If you need git hooks, you will need to set them up for each new clone.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Solution #4: git worktree
&lt;div id="solution-4-git-worktree" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-4-git-worktree" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;To use this solution, you may need to learn about &lt;code&gt;git add worktree&lt;/code&gt;. Don&amp;rsquo;t feel
bad if you&amp;rsquo;re not familiar with worktrees in Git. Many people get by for years
in blissful ignorance of this concept.&lt;/p&gt;
&lt;h3 class="relative group"&gt;What is a Worktree?
&lt;div id="what-is-a-worktree" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-is-a-worktree" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Think of a worktree as the files in the repository that belong to a project.
Essentially, it&amp;rsquo;s a kind of workspace. You may not realize that you&amp;rsquo;re already
using worktrees. When using Git, you get your first worktree for free.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ mkdir /tmp/foo &amp;amp;&amp;amp; cd /tmp/foo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git init
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git worktree list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/tmp 0000000 [main]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As you can see, the worktree exists even before the first commit. Now, add a new
worktree to an existing project.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Add a worktree
&lt;div id="add-a-worktree" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#add-a-worktree" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;To add a new worktree, you need to provide:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A location on disk&lt;/li&gt;
&lt;li&gt;A branch name&lt;/li&gt;
&lt;li&gt;Something to branch from&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git clone https://github.com/oalders/http-browserdetect.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cd http-browserdetect/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git worktree list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/Users/olaf/http-browserdetect 90772ae [main]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git worktree add ~/trees/oalders/feature-X -b oalders/feature-X origin/main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git worktree add ~/trees/oalders/feature-Y -b oalders/feature-Y e9df3c555e96b3f1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git worktree list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/Users/olaf/http-browserdetect 90772ae [main]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/Users/olaf/trees/oalders/feature-X 90772ae [oalders/feature-X]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/Users/olaf/trees/oalders/feature-Y e9df3c5 [oalders/feature-Y]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Like with most other Git commands, you need to be inside a repository when
issuing this command. Once the worktrees are created, you have isolated work
environments. The Git repository tracks where the worktrees live on disk. If Git
hooks are already set up in the parent repository, they will also be available
in the worktrees.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t overlook that each worktree uses only a fraction of the parent
repository&amp;rsquo;s disk space. In this case, the worktree requires about one-third of
the original&amp;rsquo;s disk space. This can scale very well. Once your repositories are
measured in the gigabytes, you&amp;rsquo;ll really come to appreciate these savings.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ du -sh /Users/olaf/http-browserdetect
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2.9M
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ du -sh /Users/olaf/trees/oalders/feature-X
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1.0M&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; You can work in multiple workspaces simultaneously. You don&amp;rsquo;t need the
stash. Git tracks all of your worktrees. You don&amp;rsquo;t need to set up Git hooks.
This is also faster than &lt;code&gt;git clone&lt;/code&gt; and can save on network traffic since you
can do this in airplane mode. You also get more efficient disk space use without
needing to resort to a shallow clone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; This is yet another thing to remember. However, if you can get into
the habit of using this feature, it can reward you handsomely.&lt;/p&gt;
&lt;h2 class="relative group"&gt;A few more tips
&lt;div id="a-few-more-tips" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#a-few-more-tips" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;When you need to clean up your worktrees, you have a couple of options. The
preferable way is to let Git remove the worktree:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree remove /Users/olaf/trees/oalders/feature-X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you prefer a scorched-earth approach, &lt;code&gt;rm -rf&lt;/code&gt; is also your friend:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -rf /Users/olaf/trees/oalders/feature-X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, if you do this, you may want to clean up any remaining files with
&lt;code&gt;git worktree prune&lt;/code&gt;. Or you can skip the &lt;code&gt;prune&lt;/code&gt; now, and this will happen on
its own at some point in the future via &lt;code&gt;git gc&lt;/code&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Notable notes
&lt;div id="notable-notes" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#notable-notes" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re ready to get started with &lt;code&gt;git worktree&lt;/code&gt;, here are a few things to
keep in mind.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removing a worktree does not delete the branch.&lt;/li&gt;
&lt;li&gt;You can switch branches within a worktree.&lt;/li&gt;
&lt;li&gt;You cannot, by default, simultaneously check out the same branch in multiple
worktrees. If you do want to do this, you&amp;rsquo;ll need to use the
&lt;code&gt;--ignore-other-worktrees&lt;/code&gt; option when using &lt;code&gt;git switch&lt;/code&gt; or &lt;code&gt;git checkout&lt;/code&gt;.
(Thanks to Linda O&amp;rsquo;Sullivan for this tip).&lt;/li&gt;
&lt;li&gt;Like many other Git commands, &lt;code&gt;git worktree&lt;/code&gt; needs to be run from inside a
repository.&lt;/li&gt;
&lt;li&gt;You can have many worktrees at once.&lt;/li&gt;
&lt;li&gt;Create your worktrees from the same local checkout, or they will be agnostic
about each other.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;git rev-parse
&lt;div id="git-rev-parse" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#git-rev-parse" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;One final note: When using &lt;code&gt;git worktree&lt;/code&gt;, your concept of where the root of the
repository lives may depend on context. Fortunately, &lt;code&gt;git rev-parse&lt;/code&gt; allows you
to distinguish between the two.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To find the parent repository&amp;rsquo;s root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git rev-parse --git-common-dir
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To find the root of the repository you&amp;rsquo;re in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git rev-parse --show-toplevel
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;Choose the best method for your needs
&lt;div id="choose-the-best-method-for-your-needs" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#choose-the-best-method-for-your-needs" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As in many things, TIMTOWDI (there&amp;rsquo;s more than one way to do it). What&amp;rsquo;s
important is that you find a workflow that suits your needs. What your needs are
may vary depending on the problem at hand. Maybe you&amp;rsquo;ll occasionally find
yourself reaching for &lt;code&gt;git worktree&lt;/code&gt; as a handy tool in your revision-control
toolbelt.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2024/01/09/git-context-switching/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2024/01/09/git-context-switching/featured.jpeg"/></item><item><title>is: an inspector for your environment</title><link>https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/</link><pubDate>Wed, 27 Sep 2023 19:03:23 -0400</pubDate><guid>https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/</guid><description>&lt;p align="center"&gt;&lt;img src="https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/featured.png" /&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ (is os name eq darwin &amp;amp;&amp;amp; echo 🍏 ) ||
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (is os name eq linux &amp;amp;&amp;amp; echo 🐧) ||
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; echo 💣
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ 🍏&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In a hurry? &lt;a href="https://github.com/oalders/is#readme" target="_blank"&gt;Jump straight to the README&lt;/a&gt;
or &lt;a href="https://github.com/oalders/is#installation" target="_blank"&gt;install is now&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Motivation
&lt;div id="motivation" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#motivation" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Many years ago, before I switched to 100% remote work, I sat beside a young kid
who had recently graduated from University. I picked up a lot of command line
tricks from him, mostly by looking over his shoulder. I learned two big things
from him:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;vim&lt;/code&gt; is highly configurable&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s helpful to keep your config files in a &lt;code&gt;git&lt;/code&gt; repository&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The kid had a neat way of organizing his stuff and by stuff I mean &amp;ldquo;dot files&amp;rdquo;.
I copied some of his setup and, over the years, I have customized it a lot. My
dot files get installed on Macs (sometimes with Apple silicon and sometimes
not), Ubuntu VMs, Raspberry Pi and most recently on a Chromebook (so, Debian). I
have different needs for different environments, but I want my dot files to
handle as much of this as possible. Also, I&amp;rsquo;d like to keep them in public and on
GitHub.&lt;/p&gt;
&lt;p&gt;All this means that I have to do a few things to ensure everything gets
installed in the right way and on the right machine.&lt;/p&gt;
&lt;p&gt;So, you&amp;rsquo;re probably thinking that I just moved my dot files to Ansible
configuration files.&lt;/p&gt;
&lt;p&gt;I did not.&lt;/p&gt;
&lt;p&gt;Right, but if I were sensible I would use one of the cool dot file frameworks to
deploy to my various machines.&lt;/p&gt;
&lt;p&gt;I did not.&lt;/p&gt;
&lt;p&gt;Surely I&amp;rsquo;m not just using a bunch of &lt;code&gt;bash&lt;/code&gt; scripts?&lt;/p&gt;
&lt;p&gt;Yes, I am.&lt;/p&gt;
&lt;p&gt;Now, this may not seem sensible to you, but my reasoning is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I want the files to be installable everywhere and I want the repo to
basically bootstrap itself, so if &lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;git&lt;/code&gt; are available, things
should mostly just work&lt;/li&gt;
&lt;li&gt;I want my logic to run quickly, which can be hard to do with Ansible&lt;/li&gt;
&lt;li&gt;I do not want to be constrained by someone else&amp;rsquo;s way of doing things&lt;/li&gt;
&lt;li&gt;I do want to mess around with shell scripts&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t tell me what to do&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Working within these parameters I&amp;rsquo;m able to do most of the things that I need to
do, but some things seem hard. For instance &amp;ldquo;do A if we&amp;rsquo;re on a Mac but do B on
Ubuntu (but only certain versions) or do B on Debian, but also certain versions.
At some point doing all of this in &lt;code&gt;bash&lt;/code&gt; feels kind of tedious. I wondered if I
could write a tool to give me some of these answers in a more elegant way.&lt;/p&gt;
&lt;p&gt;I eventually settled on Go as the implementation language for the tool and
&lt;a href="https://github.com/alecthomas/kong" target="_blank"&gt;kong&lt;/a&gt; as the CLI arg parser. After messing
around with &lt;code&gt;kong&lt;/code&gt;, I realized that I could probably abuse commands and
subcommands to come up with a CLI that reads (at least a little bit) like
English. The end result is: &lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt;.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Recipes
&lt;div id="recipes" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recipes" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s dive right in.&lt;/p&gt;
&lt;p&gt;Sometimes you don&amp;rsquo;t know if a binary is available. Let&amp;rsquo;s do a &lt;code&gt;git clone&lt;/code&gt; only
if &lt;code&gt;git&lt;/code&gt; is available.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is there git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git clone https://github.com/oalders/is.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Likewise, let&amp;rsquo;s install &lt;code&gt;bat&lt;/code&gt; via &lt;code&gt;homebrew&lt;/code&gt;, but only if we&amp;rsquo;re on a Mac.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is os name eq darwin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew install bat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We could further expand this and only install via &lt;code&gt;brew&lt;/code&gt; if it&amp;rsquo;s available:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is os name eq darwin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; is there brew &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew install bat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Do we have a &lt;code&gt;perl&lt;/code&gt; with a version &amp;gt;= 5.34 in our &lt;code&gt;$PATH&lt;/code&gt;?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli version perl gte 5.34 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; perl -E &lt;span class="s1"&gt;&amp;#39;say &amp;#34;TIMTOWDI&amp;#34;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s express this by checking the minor version specifically:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli version perl --minor gte &lt;span class="m"&gt;34&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Maybe we can support a range of versions:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli version perl --minor in 34,36,38&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Just print the codename of this OS version:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known os version-codename
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ventura&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Just print the system architecture:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ is known arch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;amd64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Try to upgrade &lt;a href="https://github.com/houseabsolute/ubi" target="_blank"&gt;ubi&lt;/a&gt; if it&amp;rsquo;s older than 7
days.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli age ubi gt &lt;span class="m"&gt;7&lt;/span&gt; days &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ubi --self-upgrade&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Am I running as the correct user?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stdout whoami eq olaf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Is today Tuesday?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stdout date like Tue&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Same, but without a regex:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stdout date --arg&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+%a&amp;#39;&lt;/span&gt; eq Tue&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Is today within Mon-Wed?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stdout date --arg&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+%a&amp;#39;&lt;/span&gt; like &lt;span class="s1"&gt;&amp;#39;Mon|Tue|Wed&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Same, but without a regex:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stdout date --arg&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+%a&amp;#39;&lt;/span&gt; in Mon,Tue,Wed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Is this README.md greater than 800 lines long?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stdout &lt;span class="s1"&gt;&amp;#39;bash -c&amp;#39;&lt;/span&gt; -a &lt;span class="s1"&gt;&amp;#39;cat README.md|wc -l&amp;#39;&lt;/span&gt; gte &lt;span class="m"&gt;800&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Are we on the &lt;code&gt;main&lt;/code&gt; branch in this &lt;code&gt;git&lt;/code&gt; repository?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stdout git --arg&lt;span class="o"&gt;=&lt;/span&gt;branch --arg&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--show-current&amp;#39;&lt;/span&gt; eq main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Check if &lt;code&gt;git&lt;/code&gt; supports &lt;code&gt;branch --show-current&lt;/code&gt; before running the previous
command:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli version git gte 2.22 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; is cli output stdout git --arg&lt;span class="o"&gt;=&lt;/span&gt;branch --arg&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--show-current&amp;#39;&lt;/span&gt; eq main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Use a regex to match an &lt;code&gt;SSH&lt;/code&gt; version. Note that &lt;code&gt;ssh -V&lt;/code&gt; prints to &lt;code&gt;STDERR&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli output stderr ssh --arg&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-V&amp;#39;&lt;/span&gt; like 9.0p1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The above is essentially the same as the much shorter:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is cli version ssh like 9.0p1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Get the version of a CLI and do something with it.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;is known cli version go&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1.20.6&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This is somewhat easier than parsing a version out of:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go version go1.20.6 linux/amd64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In a script, exit early if it&amp;rsquo;s a Raspberry Pi.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; is os id eq raspbian&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;At the command line, update the &lt;code&gt;go&lt;/code&gt; binaries that &lt;code&gt;vim-go&lt;/code&gt; needs:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is there go &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; nvim +:GoUpdateBinaries +qa&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Same, but ensure &lt;code&gt;nvim&lt;/code&gt; is installed:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is there go &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; is there nvim &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvim +:GoUpdateBinaries +qa&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Same, but install plugins before &lt;code&gt;:GoUpdateBinaries&lt;/code&gt; in order to ensure that
&lt;code&gt;vim-go&lt;/code&gt; has been installed:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is there go &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; is there nvim &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvim +:PlugInstall +:GoUpdateBinaries +qa&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Same, but ensure we have at least version 1.20 of &lt;code&gt;go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;is there go &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; is cli version go gte 1.20 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; is there nvim &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvim +:PlugInstall +:GoUpdateBinaries +qa&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now a more complex example. I want to install the nightly &lt;code&gt;wezterm&lt;/code&gt; build. On
macOS, I tend to use &lt;code&gt;homebrew&lt;/code&gt;, but on Linux I&amp;rsquo;ve chosen to install the &lt;code&gt;.deb&lt;/code&gt;
files. To complicate the Linux example, I don&amp;rsquo;t want to re-install the nightly
&lt;code&gt;wezterm&lt;/code&gt; if I (for whatever reason) re-run my dot files installer. So, if
&lt;code&gt;wezterm&lt;/code&gt; has been installed within the last 18 hours, we won&amp;rsquo;t try installing
it again. (See &lt;code&gt;if is cli age wezterm lt 18 hours&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;The download URLs look like:&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;&lt;a href="https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu20.04.deb" target="_blank"&gt;https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu20.04.deb&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;and&lt;/p&gt;
&lt;blockquote &gt;&lt;p&gt;&lt;a href="https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu22.04.deb" target="_blank"&gt;https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu22.04.deb&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -e -u -o pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;install_for_linux&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; sudo apt install -y libxcb-image0 libxkbcommon-x11-0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;is known os version&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wezterm-nightly.&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.deb
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://github.com/wez/wezterm/releases/download/nightly/&lt;span class="nv"&gt;$file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; curl --location --output &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; sudo dpkg -i &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; is os name eq darwin&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; is there wezterm&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; brew upgrade homebrew/cask/wezterm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; brew tap wez/wezterm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; brew install --cask wezterm --no-quarantine
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;elif&lt;/span&gt; is os name eq linux&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; ! is user sudoer&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; is there wezterm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; is cli age wezterm lt &lt;span class="m"&gt;18&lt;/span&gt; hours &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; is os id eq ubuntu &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; is os version in 20.04,22.04&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; install_for_linux Ubuntu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; is os id eq debian &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; is os version --major in 10,11&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt; install_for_linux Debian
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Hopefully this brief tutorial gives you an idea of what I had in mind with
&lt;a href="https://github.com/oalders/is" target="_blank"&gt;is&lt;/a&gt;. I wrote it entirely for my own needs. I&amp;rsquo;ve
been living with it for a couple of months. For my purposes it has been just
fine. Maybe you&amp;rsquo;ll find it useful as well.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2023/09/28/is-an-inspector-for-your-environment/featured.png"/></item><item><title>Closing Duplicate Tabs With AppleScript</title><link>https://www.olafalders.com/2023/08/03/closing-duplicate-tabs-with-applescript/</link><pubDate>Thu, 03 Aug 2023 20:47:54 -0400</pubDate><guid>https://www.olafalders.com/2023/08/03/closing-duplicate-tabs-with-applescript/</guid><description>&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/08/03/closing-duplicate-tabs-with-applescript/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="tab"
src="https://www.olafalders.com/2023/08/03/closing-duplicate-tabs-with-applescript/featured.jpeg"
&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p class="attribution"&gt;"&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/22334306@N02/2180149386"&gt;tab can&lt;/a&gt;" by &lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/22334306@N02"&gt;lokate366&lt;/a&gt; is licensed under &lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-sa/2.0/?ref=openverse"&gt;CC BY-SA 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 class="relative group"&gt;ETOOMANYTABS
&lt;div id="etoomanytabs" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#etoomanytabs" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I have a browser tab problem. I open a lot of tabs in Chrome. I&amp;rsquo;m not great
about closing them. To help me with my tab problem, I use
&lt;a href="https://chrome.google.com/webstore/detail/clutter-free-prevent-dupl/iipjdmnoigaobkamfhnojmglcdbnfaaf" target="_blank"&gt;Clutter Free&lt;/a&gt;.
It prevents duplicate tabs from being opened. This works really well, but I
can&amp;rsquo;t use this extension at $work.&lt;/p&gt;
&lt;p&gt;At $work, I&amp;rsquo;m in an environment where I can only install Chrome extensions which
have been approved by the org administrator. I could ask to get a new extension
approved. It&amp;rsquo;s not a particularly onerous process, but I wondered if I could
just script something to run on my Mac. That would allow me to solve the problem
without a 3rd party Chrome extension.&lt;/p&gt;
&lt;p&gt;In the past, I have copy/pasted AppleScript, but I don&amp;rsquo;t think I&amp;rsquo;ve ever written
any from scratch. Maybe &lt;code&gt;say &amp;quot;You look marvelous&amp;quot;&lt;/code&gt;. That&amp;rsquo;s probably the extent
of it.&lt;/p&gt;
&lt;p&gt;I did, however, know that AppleScript can cycle through tabs. So, I came up with
a plan to write some AppleScript that could cycle through all of the Chrome tabs
on my Mac and close the duplicates. This solution doesn&amp;rsquo;t prevent duplicates
from being opened, but it does close them after the fact and that mostly gets me
to the same place.&lt;/p&gt;
&lt;p&gt;I came up with the following:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-applescript" data-lang="applescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#!/usr/bin/osascript&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;say&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;starting tab cleanup&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;urls&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;tell&lt;/span&gt; &lt;span class="nb"&gt;application&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Google Chrome&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;activate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;repeat&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;theWindow&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;every&lt;/span&gt; &lt;span class="na"&gt;window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;toClose&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;tabIndex&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;repeat&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;theTab&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;every&lt;/span&gt; &lt;span class="no"&gt;tab&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;theWindow&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;tabIsDuplicate&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;repeat&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;seen&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;urls&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;seen&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="nc"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;URL&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;theTab&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="nc"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;copy&lt;/span&gt; &lt;span class="nv"&gt;tabIndex&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;the&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;toClose&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;tabIsDuplicate&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="k"&gt;repeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;repeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;tabIndex&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;tabIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;tabIsDuplicate&lt;/span&gt; &lt;span class="ow"&gt;is not&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;copy&lt;/span&gt; &lt;span class="nv"&gt;URL&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;theTab&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="nc"&gt;string&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;the&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;urls&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;repeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;closing&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;reverse&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;toClose&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;repeat&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;closeIndex&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;closing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;close&lt;/span&gt; &lt;span class="no"&gt;tab&lt;/span&gt; &lt;span class="nv"&gt;closeIndex&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;theWindow&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;repeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;numberOfClosed&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;closing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;numberOfClosed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;numberOfClosed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;say&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tabs closed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;repeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;say&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;finished tab cleanup&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This brings Chrome to the forefront and cycles through each open Chrome window.
For each window, it cycles through every tab. If the tab&amp;rsquo;s URL has already been
seen, the script stores the tab number in a list of tabs we will need to close.
Once all tabs in a window have been checked, we reverse the list of tab numbers
that contain duplicate URLs and close them one by one. Then we move on to the
next window and continue the process, until we run out of open windows.&lt;/p&gt;
&lt;p&gt;You may ask yourself why we need to reverse the list of tab numbers before we
close them? We do this because it makes it easier to avoid off-by-one errors.
Let&amp;rsquo;s assume we need to close tabs 3, 5 and 7. Once we close tab 3, tabs 5 and 7
become tabs 4 and 6. We could handle this by decrementing those numbers, but if
we just take the list in reverse this will never happen. For instance, with the
3, 5 and 7 scenario, when tab 7 has been closed, tabs 3 and 5 remain in the same
position, so there is less state to keep track of.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Caveat Emptor
&lt;div id="caveat-emptor" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#caveat-emptor" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The script above will close tabs with extreme prejudice. So, if you&amp;rsquo;re doing
something important in a duplicate tab or you wanted to close the first tab
rather than the second, you&amp;rsquo;re out of luck. This suits my workflow fine, but
maybe not yours.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Getting the Script
&lt;div id="getting-the-script" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#getting-the-script" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;You can copy/paste the script directly from this post or
&lt;a href="https://github.com/oalders/dot-files/blob/main/bin/chrome-tab-cleanup.scpt" target="_blank"&gt;download it from my dot files repo&lt;/a&gt;.
It can be run directly at the command line. You can also run it via the
&lt;code&gt;Script Editor&lt;/code&gt; app, which comes with macOS. That&amp;rsquo;s handy for debugging.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Addendum
&lt;div id="addendum" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#addendum" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;As a satisfied &lt;a href="https://www.hammerspoon.org/" target="_blank"&gt;Hammerspoon&lt;/a&gt; user, I&amp;rsquo;ve added this
script to my Hammerspoon config. That means I can now run my tab cleanup with a
keyboard shortcut. In my case that&amp;rsquo;s &lt;code&gt;caps lock + a&lt;/code&gt;. I won&amp;rsquo;t get into
Hammerspoon configuration today, but
&lt;a href="https://github.com/oalders/dot-files/blob/main/hammerspoon/init.lua" target="_blank"&gt;my config file is on GitHub&lt;/a&gt;
and large parts of it were stolen directly from
&lt;a href="https://github.com/2shortplanks" target="_blank"&gt;Mark Fowler&lt;/a&gt;. It may be helpful to point out
that I&amp;rsquo;m using &lt;a href="https://karabiner-elements.pqrs.org/" target="_blank"&gt;Karabiner Elements&lt;/a&gt; to map
&lt;code&gt;caps lock&lt;/code&gt; to &lt;code&gt;command+control+option+shift&lt;/code&gt;.
&lt;a href="https://github.com/oalders/dot-files/blob/main/karabiner/karabiner.json" target="_blank"&gt;That config is also on GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/08/03/closing-duplicate-tabs-with-applescript/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2023/08/03/closing-duplicate-tabs-with-applescript/featured.jpeg"/></item><item><title>Whither Perl</title><link>https://www.olafalders.com/2023/07/19/2023-07-19-whither-perl/</link><pubDate>Wed, 19 Jul 2023 10:23:39 -0400</pubDate><guid>https://www.olafalders.com/2023/07/19/2023-07-19-whither-perl/</guid><description>&lt;p&gt;I recently spoke about the future of Perl at The Perl and Raku Conference.
&lt;a href="https://www.olafalders.com/slides/whither-perl/" &gt;The slides are now available&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Press the &lt;code&gt;s&lt;/code&gt; key if you&amp;rsquo;d like to view the speaker notes for the slides.)&lt;/p&gt;
&lt;p&gt;The video has been posted as well:&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/areXIy7UKyA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 class="relative group"&gt;Correction
&lt;div id="correction" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#correction" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;In the video I mentioned specifically &lt;a href="https://learn.perl.org" target="_blank"&gt;learn.perl.org&lt;/a&gt;
and assumed that it had no active ownership. I&amp;rsquo;ve now learned that *.perl.org
is the property of the Perl NOC, who have owned and maintained these domains for
decades. So, I&amp;rsquo;d like to thank them for their stewardship. Given this
information, I&amp;rsquo;d like to retract my wish to fold these properties into perl.com.
I do still think that learn.perl.org could use some updated information. Perhaps
a learning section could be added to perl.com instead? It&amp;rsquo;s something to think
about.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/07/19/2023-07-19-whither-perl/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Using Docker to Fix a Perl 5.37 Test Failure</title><link>https://www.olafalders.com/2023/06/05/docker-fix-perl-test-failure/</link><pubDate>Mon, 05 Jun 2023 00:00:00 +0000</pubDate><guid>https://www.olafalders.com/2023/06/05/docker-fix-perl-test-failure/</guid><description>&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/06/05/docker-fix-perl-test-failure/featured.jpeg" &gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="calendar"
src="https://www.olafalders.com/2023/06/05/docker-fix-perl-test-failure/featured.jpeg"
&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p class="attribution"&gt;"&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/134416355@N07/31518965950"&gt;Tux and Docker&lt;/a&gt;" by &lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/134416355@N07"&gt;maijo━&lt;/a&gt; is licensed under &lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-sa/2.0/?ref=openverse"&gt;CC BY-SA 2.0 &lt;img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" style="height: 1em; margin-right: 0.125em; display: inline;"&gt;&lt;/img&gt;&lt;/a&gt;. &lt;/p&gt;
&lt;h2 class="relative group"&gt;It All Begins with a Bug
&lt;div id="it-all-begins-with-a-bug" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#it-all-begins-with-a-bug" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;I recently got &lt;a href="https://github.com/oalders/open-this/issues/48" target="_blank"&gt;a bug report&lt;/a&gt;
via GitHub that a test in &lt;a href="https://metacpan.org/pod/Open::This" target="_blank"&gt;Open::This&lt;/a&gt; is
failing on Perl 5.37. Let&amp;rsquo;s take a look at it using
&lt;a href="https://cli.github.com/" target="_blank"&gt;gh&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;gh issue view 48
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;t/open-this.t fails with perl 5.37.x #48
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;Open • eserte opened about 1 day ago • 0 comments
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; I see the following test failure with recent bleadperls (maybe related to changed serialization of booleans):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; # Failed test &amp;#39;line 3&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; # at t/open-this.t line 167.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; # +----+------------------------------------------------+------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; # | Elt|Got |Expected |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; # +----+------------------------------------------------+------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; # | 0|{ |{ |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; # | 1| file_name =&amp;gt; &amp;#39;t/lib/Foo/Bar.pm&amp;#39;, | file_name =&amp;gt; &amp;#39;t/lib/Foo/Bar.pm&amp;#39;, |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; # * 2| is_module_name =&amp;gt; !!1, | is_module_name =&amp;gt; 1, *
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; # | 3| line_number =&amp;gt; 3, | line_number =&amp;gt; 3, |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; # | 4| original_text =&amp;gt; &amp;#39;Foo::Bar::do_something()&amp;#39;, | original_text =&amp;gt; &amp;#39;Foo::Bar::do_something()&amp;#39;, |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; # | 5| sub_name =&amp;gt; &amp;#39;do_something&amp;#39; | sub_name =&amp;gt; &amp;#39;do_something&amp;#39; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; # | 6|} |} |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; # +----+------------------------------------------------+------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; # Looks like you failed 1 test of 59.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; t/open-this.t ..........
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; Dubious, test returned 1 (wstat 256, 0x100)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; Failed 1/59 subtests
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;View this issue on GitHub: https://github.com/oalders/open-this/issues/48&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Although the layout of this blog makes it hard to see, the failure in question
is on line 15. The test is expecting a value of &lt;code&gt;1&lt;/code&gt; for the key
&lt;code&gt;is_module_name&lt;/code&gt;. However, under Perl 5.37 we get &lt;code&gt;!!1&lt;/code&gt; instead. As the ticket
suggests, this looks like an issue with how booleans are represented in Perl
5.37. Since some permutation of Perl 5.37 will soon be released in production
code as Perl 5.38, it&amp;rsquo;s a good idea to fix this test failure now. I asked for
guidance on &lt;code&gt;#toolchain&lt;/code&gt; on irc.perl.org and
&lt;a href="https://metacpan.org/author/ILMARI" target="_blank"&gt;ilmari&lt;/a&gt; helpfully let me know that there
are two options to fix this across Perl versions.&lt;/p&gt;
&lt;p&gt;One option was to use
&lt;a href="https://metacpan.org/pod/Test2::Tools::Compare#$check-=-T%28%29" target="_blank"&gt;Test2::Tools::Compare&lt;/a&gt;
for the comparison. The other was to change the expected value in the test from
&lt;code&gt;1&lt;/code&gt; to &lt;code&gt;!!1&lt;/code&gt;. I really like using
&lt;a href="https://metacpan.org/pod/Test::Differences" target="_blank"&gt;Test::Differences&lt;/a&gt;, so I&amp;rsquo;m going to
change the expected value.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have Perl 5.37 installed locally and because I&amp;rsquo;m in a hurry, I don&amp;rsquo;t
want to use &lt;code&gt;plenv&lt;/code&gt; to install it. Let&amp;rsquo;s use &lt;code&gt;Docker&lt;/code&gt; instead, because that&amp;rsquo;s
fairly quick and easy.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Sounds Like a Job for Docker
&lt;div id="sounds-like-a-job-for-docker" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sounds-like-a-job-for-docker" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;On host:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -it --rm -v $PWD:/app perl:5.37 bash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will drop you into a running Docker container. In the container itself, we
can run the following commands.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cd /app &amp;amp;&amp;amp; \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cpm install -g --cpanfile cpanfile &amp;amp;&amp;amp; \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;prove -lvr t&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Alternatively, we can also do this without having to type in the container at
all. The difference will be that our container will go away once the command has
run to completion. This is handy for really quick things. On my machine it takes
~4 seconds once the Docker images have all been downloaded.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -it --rm -v $PWD:/app \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;perl:5.37 \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bash -c \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;cd /app &amp;amp;&amp;amp; cpm install -g --cpanfile cpanfile &amp;amp;&amp;amp; prove -lvr t&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The output I get is:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Failed test &amp;#39;line 3&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# at t/open-this.t line 167.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# +----+------------------------------------------------+------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# | Elt|Got |Expected |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# +----+------------------------------------------------+------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# | 0|{ |{ |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# | 1| file_name =&amp;gt; &amp;#39;t/lib/Foo/Bar.pm&amp;#39;, | file_name =&amp;gt; &amp;#39;t/lib/Foo/Bar.pm&amp;#39;, |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# * 2| is_module_name =&amp;gt; !!1, | is_module_name =&amp;gt; 1, *
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# | 3| line_number =&amp;gt; 3, | line_number =&amp;gt; 3, |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# | 4| original_text =&amp;gt; &amp;#39;Foo::Bar::do_something()&amp;#39;, | original_text =&amp;gt; &amp;#39;Foo::Bar::do_something()&amp;#39;, |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# | 5| sub_name =&amp;gt; &amp;#39;do_something&amp;#39; | sub_name =&amp;gt; &amp;#39;do_something&amp;#39; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# | 6|} |} |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# +----+------------------------------------------------+------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="relative group"&gt;Fixing the Bug
&lt;div id="fixing-the-bug" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#fixing-the-bug" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve been able to replicate the bug. 🥳 On the host machine, I&amp;rsquo;ll use the
&lt;a href="https://metacpan.org/dist/Open-This/view/script/ot" target="_blank"&gt;ot&lt;/a&gt; script which comes via
&lt;code&gt;Open::This&lt;/code&gt; to open the file locally at the appropriate line in my favourite
editor.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ot t/open-this.t line 167&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then I&amp;rsquo;ll apply the following change:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/t/open-this.t b/t/open-this.t
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index a2e750e..f0b6220 100644
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/t/open-this.t
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/t/open-this.t
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -168,7 +168,7 @@ local $ENV{EDITOR} = &amp;#39;vim&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; parse_text($text),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file_name =&amp;gt; &amp;#39;t/lib/Foo/Bar.pm&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- is_module_name =&amp;gt; 1,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ is_module_name =&amp;gt; !!1,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; line_number =&amp;gt; 3,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; original_text =&amp;gt; $text,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sub_name =&amp;gt; &amp;#39;do_something&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now, when I run the tests again, they are passing. 🎉 Many thanks to
&lt;a href="https://metacpan.org/author/SREZIC" target="_blank"&gt;SREZIC&lt;/a&gt; for once again helpfully pointing
test failures out to me and &lt;a href="https://metacpan.org/author/ILMARI" target="_blank"&gt;ILMARI&lt;/a&gt; for
showing me how to fix the test. 🙏&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2023/06/05/docker-fix-perl-test-failure/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2023/06/05/docker-fix-perl-test-failure/featured.jpeg"/></item></channel></rss>