<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="pretty-atom-feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Heckinchonkeires&#39; Blog</title>
  <subtitle>A blog about things and stuff.</subtitle>
  <link href="https://web.heckinchonkeires.me/feed/feed.xml" rel="self" />
  <link href="https://web.heckinchonkeires.me/" />
  <updated>2024-11-16T00:00:00Z</updated>
  <id>https://web.heckinchonkeires.me/</id>
  <author>
    <name>Heckinchonkeires</name>
  </author>
  <entry>
    <title>How Microsoft and Apple Pushed Me Into Using Linux</title>
    <link href="https://web.heckinchonkeires.me/blog/linux-server-setup/" />
    <updated>2024-11-16T00:00:00Z</updated>
    <id>https://web.heckinchonkeires.me/blog/linux-server-setup/</id>
    <content type="html">&lt;h2 id=&quot;setting-up-open-source-software-is-worth-the-effort&quot;&gt;&lt;strong&gt;Setting up open source software is worth the effort&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This post is about what it took for me to figure out the best way of working from my phone, and how obstacles created by Microsoft and Apple influenced where I ended up.&lt;/p&gt;
&lt;p&gt;I’ve had plenty of gripes with big tech companies over the many years I’ve used their products. I’m well into the process of extricating myself from their systems today. They aren&#39;t making it easy, but I&#39;ve already found plenty of alternatives that work well for me. Over the past few years, I switched from using Twitter (it was still Twitter when I deleted my account) to Mastodon, I started transitioning my primary email away from Gmail, I switched my primary browser from Chrome to Firefox, and I switched my default search engine to DuckDuckGo. Thanks to &lt;a href=&quot;https://nebula.tv/featured&quot;&gt;Nebula&lt;/a&gt;, some of my favourite YouTube content is available outside of Google&#39;s ecosystem, but &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_effect&quot;&gt;network effect&lt;/a&gt; still forces me to use the platform. I&#39;ve been thinking about trying Ubuntu as an operating system for some time, but my primary use for a desktop computer historically has been gaming. Linux gaming has progressed a lot in large part thanks to &lt;a href=&quot;https://store.steampowered.com/steamos&quot;&gt;SteamOS&lt;/a&gt;, but I still begrudgingly want to have access to a Windows installation. Apple hasn&#39;t yet managed to push me away from iOS, but I&#39;m sure it&#39;s only a matter of time, so I&#39;m thinking about making the jump myself first.&lt;/p&gt;
&lt;p&gt;After some struggle, I no longer really need my PC for working on software projects. I can do almost everything from my iPhone. (&lt;em&gt;Not being able to do one important thing seems to be entirely on Apple and I&#39;ll address it specifically later in this post.&lt;/em&gt;) I hope it will be interesting to detail the things I tried to get to this point that didn&#39;t work, and the things I had to learn along the way.&lt;/p&gt;
&lt;h3 id=&quot;giving-windows-one-last-chance&quot;&gt;Giving Windows one last chance&lt;/h3&gt;
&lt;p&gt;At the time I published my first post here, I had only worked on this project using VS Code on Windows. Since then, circumstances have necessitated me working from bed most of the time. I don&#39;t mind working on my Windows laptop in bed, but being a chunky old gaming laptop makes it less than ideal for doing so for long periods of time. A more ideal device for coding in bed is outside my current means, so my best option was using my iPhone. I didn&#39;t relish the thought of transitioning from two moderately sized monitors to a screen that fits it my hand, but I was determined to make it work as well as it could.&lt;/p&gt;
&lt;p&gt;The first iOS app I tried was &lt;a href=&quot;https://ish.app&quot;&gt;iSH&lt;/a&gt;. It&#39;s totally free, and it emulates &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt; with a file system that&#39;s accessible through the native Files app. It&#39;s limited by being on the App Store, but I think its developers have done admirably within those limitations. Apple&#39;s Files not showing hidden files was a significant one. See the scripts below for how I got around it.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;The Bash scripts I wrote to deal with hidden files in iSH&lt;/summary&gt;
&lt;p&gt;&lt;code&gt;make_hidden.sh&lt;/code&gt;:
I wrote this first because I was working on files called &lt;code&gt;bashrc&lt;/code&gt; and &lt;code&gt;bash_profile&lt;/code&gt;, and I needed versions that started with &lt;code&gt;.&lt;/code&gt; so Bash would recognize them.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# takes an arbitrary list of file name arguments and creates hidden versions of them&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; already exists. Overwrite? (Y/n): &quot;&lt;/span&gt; response
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Yy&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
				&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt;
				&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt;
				&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; overwritten&quot;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
				&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; skipping &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; response
		&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
		&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; not found&quot;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; arg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;unhide.sh&lt;/code&gt;:
Later I needed this similar script so I could &amp;quot;unhide&amp;quot; copies of hidden files from my server for editing.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# takes an arbitrary list of hidden file name arguments and creates non-hidden versions of them&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
		&lt;span class=&quot;token assign-left variable&quot;&gt;unhidden&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${arg&lt;span class=&quot;token operator&quot;&gt;##&lt;/span&gt;*&quot;.&quot;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$unhidden&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$unhidden&lt;/span&gt; already exists. Overwrite? (Y/n): &quot;&lt;/span&gt; response
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Yy&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
				&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$unhidden&lt;/span&gt;
				&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$unhidden&lt;/span&gt;
				&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$unhidden&lt;/span&gt; overwritten&quot;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
				&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; skipping &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; response
		&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$unhidden&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; unhidden
		&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
		&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$arg&lt;/span&gt; not found&quot;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; arg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to my latest (hopefully final) setup I don&#39;t need these anymore.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;My recently refreshed Git knowledge gave me an idea: I didn&#39;t want to store every single file necessary for my projects on my phone, so perhaps I could figure out how to use Git to work with only a subset of a repository. I was stymied by outdated information on workarounds for that use case, then I found the simpler, modern solution after thoroughly reading a thread on &lt;code&gt;&amp;lt;well-known QA service&amp;gt;&lt;/code&gt;. &lt;a href=&quot;https://git-scm.com/docs/git-sparse-checkout&quot;&gt;&lt;code&gt;git-sparse-checkout&lt;/code&gt;&lt;/a&gt; is still marked as experimental, so I won&#39;t claim it&#39;s the correct way to do things, just that it worked exactly as expected for the time I used it.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;git-sparse-checkout&lt;/code&gt; and iSH, I was able to download only the website content to my phone, modify it, and push it wherever I needed it. Though if all I needed to do was compose and upload posts, I would have found a much easier way to do it. I still needed my Windows machine to actually publish the site, as well as–critically–run the development server so I could test new pages in a browser. I briefly considered trying to automate things using &lt;a href=&quot;https://git-scm.com/docs/githooks&quot;&gt;Git hooks&lt;/a&gt;, but it seemed like too much work, so I decided I needed to get more software involved.&lt;/p&gt;
&lt;p&gt;A component of one of my university courses was learning how to work with a &lt;a href=&quot;https://www.raspberrypi.com/products/raspberry-pi-zero-w/&quot;&gt;Raspberry Pi Zero W&lt;/a&gt; over WiFi with OpenSSH. So I had a little experience with SSH, and I was aware of how ubiquitous it was. That course had only covered using SSH clients outside of Linux, so I needed a little help in the form of &lt;a href=&quot;https://www.hanselman.com/blog/how-to-ssh-into-a-windows-10-machine-from-linux-or-windows-or-anywhere&quot;&gt;this tutorial&lt;/a&gt; by Scott Hanselman to set up the Windows OpenSSH server. After working through some additional Windows issues not covered by the tutorial, I had &lt;code&gt;sshd&lt;/code&gt; running on my PC. Through iSH on my phone, I was then able to manage the Eleventy development server and publish the site. Manually using Git to sync files was still a major annoyance, but I got too sidetracked to consider Git hooks again.&lt;/p&gt;
&lt;p&gt;Next, I took a significant detour trying to &amp;quot;improve&amp;quot; remote development on my PC. I dabbled with some &lt;a href=&quot;https://web.heckinchonkeires.me/blog/bash-scripting&quot;&gt;Bash scripting&lt;/a&gt;, and I tried to make Windows run without a GUI, all the time feeling I had to wrestle Windows into doing what I wanted–if it let me do what I wanted at all. A couple of my &amp;quot;favourite&amp;quot; problems were having to remove &lt;code&gt;/dev/null&lt;/code&gt; in a Bash script because Git Bash couldn&#39;t use the Windows equivalent, &lt;code&gt;NUL&lt;/code&gt;, and having to resort to adding the location of &lt;code&gt;bash.exe&lt;/code&gt; specifically to Windows&#39; &lt;code&gt;PATH&lt;/code&gt; environment variable to get Windows to find it, despite Windows having no trouble finding &lt;code&gt;git.exe&lt;/code&gt; in the same folder. Eventually, though I hadn&#39;t exhausted my options, I had exhausted my patience for Microsoft&#39;s nonsense. I no longer cared if it was going to take more work to set up a Linux server to do the same things. I was finally ready to put the work in to embrace open source software.&lt;/p&gt;
&lt;h3 id=&quot;the-home-ubuntu-server&quot;&gt;The home Ubuntu server&lt;/h3&gt;
&lt;p&gt;If I were to make a recommendation to a friend who was also sick of Microsoft and Apple operating systems, I&#39;d tell them to grab the latest LTS version of &lt;a href=&quot;https://ubuntu.com/download/desktop&quot;&gt;Ubuntu Desktop&lt;/a&gt;. I decided to make things a little more difficult for myself. When I was first considering installing Linux on my old PC, I thought I wanted to self-host a website. I had removed the graphics card and all the storage save the now pitifully small 128 gigabyte SSD, so installing an entire GUI seemed like a waste of resources. I installed &lt;a href=&quot;https://ubuntu.com/server&quot;&gt;Ubuntu Server&lt;/a&gt; and tinkered with it for a few days.  I didn&#39;t get very far before I lost interest. (&lt;em&gt;This was before I had ever used SSH, so constantly having to move between the server and my new PC didn&#39;t make for a great experience.&lt;/em&gt;) When Windows made me resolve to spend my effort on an operating system I could actually control, my old PC still had that older version of Ubuntu Server installed. I had a much better idea of what I wanted to do, and Ubuntu Server still looked like the operating system for the job.&lt;/p&gt;
&lt;p&gt;The first hurdle I encountered was the original Ubuntu Server installation. When I tried to update to the latest version of the operating system, the update failed twice. On top of that, I had elected to fully encrypt the storage drive. I hadn&#39;t lost the encryption password, but I convinced myself that I would have to physically access the computer to enter the encryption password every time it restarted. (&lt;em&gt;I now think there could be some way to decrypt a fully encrypted drive remotely, but finding out how hasn&#39;t been a priority.&lt;/em&gt;) Some cursory research indicated that removing the encryption was effectively impossible, so I decided I might as well pave the whole thing and start over. I hadn&#39;t left anything I cared about on that drive anyway.&lt;/p&gt;
&lt;p&gt;Getting a fresh Ubuntu Server installation on that machine proved more complicated than I expected. I don&#39;t know if it was the encrypted drive, the older version of &lt;a href=&quot;https://etcher.balena.io/&quot;&gt;Balena Etcher&lt;/a&gt; I used, the wonky existing Ubuntu installation, or something else entirely, but the first fresh installation failed so spectacularly that it left the operating system inoperable and made a complete mess of the USB drive I used. Never before had I plugged in a USB drive that Windows couldn&#39;t even recognize well enough to demand it be formatted. Fortunately, Windows&#39; Disk Management could still see it, so I hadn&#39;t managed to completely destroy it. To be safe, I gave it a full format and flashed it with the new Ubuntu Server image using the latest version of &lt;a href=&quot;http://rufus.ie/en/&quot;&gt;Rufus&lt;/a&gt;. The second installation worked, but I must have managed to type the same wrong character(s) twice while setting the administrator password, because I got locked out of the system without ever having logged in. I was too frustrated to consider whether waiting for the lock to expire would be faster than another full installation, so I did the thing I could do immediately and started installing Ubuntu for the third time. At least I didn&#39;t have to flash the USB drive again. That time I had no trouble logging in, so I started configuring things.&lt;/p&gt;
&lt;p&gt;I wanted to get to the point where I could work on the server from my phone as soon as possible. Sitting on my couch hunched over a small old VGA monitor supported by a convenient folding chair was even worse than working at my desk. Unlike the first time I configured Ubuntu Server, I knew exactly what I needed to do. I updated the pre-installed software packages and assigned the server a static IP address using my router, since I didn&#39;t want to learn how to set a static IP on the command line just then. I configured the firewall to deny all incoming traffic except from my local network using &lt;code&gt;ufw&lt;/code&gt;, and set up the SSH server so I could connect with iSH. Copying the public SSH encryption key from iSH to the server using OpenSSH&#39;s provided script worked the first time, unlike with Windows. Once I rebooted and tested the SSH connection one more time, I was confident I could do everything else from my phone.&lt;/p&gt;
&lt;h3 id=&quot;getting-ios-and-ubuntu-in-unison&quot;&gt;Getting iOS and Ubuntu in &lt;code&gt;unison&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In less time than I would have anticipated a year prior, I was at the same place with that Ubuntu server I had gotten to with my PC. I could easily manage the Eleventy development server, upload and download files, and publish to my Codeberg Pages repository from anywhere in my WiFi network&#39;s range. I could have tolerated that situation, but manually syncing files between devices was still getting in the way. I wasn&#39;t sure what I needed specifically, so I did some searching for how to handle remote software development on an iPhone. I eventually found some useful tools to test, but I had to get pretty specific with my queries to not get pages of results about remote iOS developer jobs.&lt;/p&gt;
&lt;p&gt;I gave &lt;a href=&quot;https://workingcopy.app/&quot;&gt;Working Copy&lt;/a&gt; a try, but quickly determined it wasn&#39;t the right tool. It seemed great at streamlining working with Git on iOS, but it didn&#39;t seem to do anything I couldn&#39;t do using iSH with a little more effort. I came across two iOS code editor apps in the same listicle that sounded promising: &lt;a href=&quot;https://buffereditor.com/&quot;&gt;Buffer&lt;/a&gt; and &lt;a href=&quot;https://www.textasticapp.com/&quot;&gt;Textastic&lt;/a&gt;. They were both one time purchases with no additional monetization (which is always a good sign to me), and they happened to be the same price. Textastic was the #1 app in the developer tools category, with better reviews, better documentation, and more customization. Buffer, however, had a pretty slick website, and it listed a killer feature there that I couldn&#39;t find anywhere in Textastic&#39;s excellent &lt;a href=&quot;https://www.textasticapp.com/v10/manual/&quot;&gt;manual&lt;/a&gt;: &amp;quot;Edit files directly on your server without download/upload.&amp;quot; If Buffer could do that while being a reasonably useable code editor, nothing Textastic could offer could compare. That did not turn out to be the case. I&#39;m not writing this post using Buffer.&lt;/p&gt;
&lt;p&gt;I paid my $12.99 plus tax for Buffer, and after quick look at the settings menu, got straight to the point. Buffer instantly failed to connect to my server, and offered an unhelpfully vague error message. I double-checked the SSH settings and tested them again in iSH, and they worked perfectly there. I gave the connection a few more tries in Buffer, but kept getting the same vague error. Then I decided that if I was going to have to put in a lot more effort to get the app to do the thing I bought it for, I should find out how it was as an editor first. I think that decision ended up saving me some frustration, if not some time. Over the course of an hour trying to work on local files, Buffer froze and/or crashed at least five times. Immediately after the app recovered from one last crash, an iOS standard &amp;quot;If you&#39;re enjoying &lt;code&gt;&amp;lt;app&amp;gt;&lt;/code&gt;, please rate it&amp;quot; pop up appeared. I normally dismiss those without much thought, but this time I had plenty to say about the app. (&lt;em&gt;I do try to remember to write reviews for apps I really like, but I much prefer to do it on my own terms.&lt;/em&gt;) So I gave Buffer a one star rating, left a review explaining why, uninstalled it, and requested a refund–which I later received. A couple search queries later, I actually managed to find an article that discussed remote software development on an iPhone. I&#39;m kicking myself for not bookmarking it at the time, because I can&#39;t find it again and I&#39;d really like to credit the author. It goes over a few options, but I didn&#39;t spend much time thinking about most of them, because the first one sounded great. &lt;a href=&quot;https://github.com/coder/code-server&quot;&gt;code-server&lt;/a&gt;: remote-hostable VS Code with a browser interface.&lt;/p&gt;
&lt;p&gt;Despite my growing animosity toward Microsoft, I&#39;m still reasonably happy with VS Code. It happened to be the IDE used in a tutorial I followed years ago, so I picked it up it without much thought, and kept using it as it handled everything I needed an IDE to do well. So I thought code-server would mean using the tool I was most comfortable with from my phone, as well as from any other device with a web browser. I&#39;m sad to also report that I am not writing this post in a web browser. I can&#39;t recall much about what it took to get code-server to even render a web page. I probably had to forward a port or two, and I know I even tried building it from source code before giving up. The best I got out of code-server with my setup was even worse than Buffer. I actually edited and saved a file or two using Buffer. code-server&#39;s web interface was so unresponsive for me that I never even got a file open. Unlike with Buffer, I think the failing is more likely in myself or my setup than the software. I think either I didn&#39;t find the right way to configure things for my situation, or something about my hardware makes running a useable code-server instance impossible (the iPhone part if I had to guess).&lt;/p&gt;
&lt;p&gt;So I searched yet again. I found &lt;a href=&quot;https://willem.com/blog/2020-09-16_syncing-files-seamlessly-between-smartphone-and-tablet/&quot;&gt;this article&lt;/a&gt; by Willem Middelkoop, which would (eventually) lead me to the solution I&#39;m using today. It describes using the cross-platform software &lt;a href=&quot;https://github.com/bcpierce00/unison&quot;&gt;Unison&lt;/a&gt; to synchronize files from a tablet to a remote server, then using the app &lt;a href=&quot;https://shellfishapp.com/&quot;&gt;Secure Shellfish&lt;/a&gt; to integrate that remote server into the native Files app on an iPhone. Unison being free and open source made it the obvious thing for me to try first. I thought I wouldn&#39;t even need another iOS app if I could just keep the files I was working on synchronized between iSH and my server.&lt;/p&gt;
&lt;p&gt;Then I ran into a major limitation of iSH for the first time: its emulation is impressive, but it isn&#39;t capable of everything standard Alpine Linux can do. On top of that, the software package repositories available through iSH have to be custom ones approved by Apple. (&lt;em&gt;I since found a &lt;a href=&quot;https://github.com/ish-app/ish/wiki/Using-Alpine-Linux-repositories&quot;&gt;cool workaround&lt;/a&gt; that lets you use the official Alpine repositories on the iSH wiki, but that only removes the Apple-imposed limitation.&lt;/em&gt;) So the software you can run in iSH is mostly limited to older versions. Unison was available, so I tried getting the default versions of Unison on Ubuntu and iSH to talk to each other. I had no luck with that straightforward approach. I noticed the Unison versions reported by the two systems were different, so even though I didn&#39;t yet know backwards compatibility was a well understood problem with older versions of Unison, it occurred to me to try matching the server version with the iSH version. I must have missed something easy and/or obvious, because I&#39;m still not clear on the correct way to get an older version of a package, either from the default Ubuntu repository or from GitHub. What came closest to working was downloading the source code for the right Unison version and building it right on the server. (&lt;em&gt;I think it&#39;s likely I made the mistake of using the wrong compiler version. My later reading told me that even two instances of the same older version of Unison compiled with two different compiler versions wouldn&#39;t work together. With the difficulty I had getting an old version of Unison, I suspect I couldn&#39;t be bothered to do the same thing for the compiler and just used whatever version Ubuntu gave me.&lt;/em&gt;) The freshly compiled Unison made it through the tutorial in Unison&#39;s manual without a hitch, and showed no signs of trouble as I configured it. Unfortunately when I tried to start syncing files I actually wanted to work on, I ran into error after error and eventually couldn&#39;t think of anything else to try.&lt;/p&gt;
&lt;p&gt;Then I was back to DuckDuckGo, but with a more specific idea of what I wanted. Sure I couldn&#39;t get Unison working, but there had to be other file synchronization software out there, and maybe that would work in iSH. &lt;a href=&quot;https://syncthing.net/&quot;&gt;Syncthing&lt;/a&gt; sounded better than Unison, and I even found a version of it I could install in iSH. The fact that I found it in the Alpine repositories and not the iSH ones was a warning sign I ignored. The initial run of Syncthing caused a kernel panic for iSH, something I hadn&#39;t seen it do before that point. So I have to admit Apple is a little right in restricting iSH&#39;s available packages. Some guardrails are warranted, but I think a warning prompt before installing known unstable or untested packages would be enough. Having done what felt like the most I could do with the free tools available, it was time to just pay somebody to solve the problem for me.&lt;/p&gt;
&lt;h3 id=&quot;the-right-tools&quot;&gt;The right tools&lt;/h3&gt;
&lt;p&gt;In the process of struggling with Unison, I installed and started using Shellfish. It was a significant terminal upgrade over iSH, and, most importantly, it had the aforementioned feature of integrating a remote server with the Files app. What kept me on the file synchronization path for so long was that full Files integration was a feature only available in the pro version of Shellfish. My time isn&#39;t worth much right now, so if I can save any amount of money by spending time, especially spending time on something fun and/or interesting, I&#39;m going to do it. By the time Syncthing was causing a kernel panic in iSH, I was bored and frustrated. I wanted to get to work on my Anytype project (&lt;em&gt;more on that soon, I promise&lt;/em&gt;), and all I had to do was cough up some cash.&lt;/p&gt;
&lt;p&gt;So I bought the pro version of Shellfish. $50 for the lifetime pro unlock was a lot more than I&#39;m comfortable spending on an iOS app, but the other option was a subscription, and I much prefer to pay for something once and get it over with when I can afford to. Also, I really like Shellfish. The ads in the free version can be disruptive, but they&#39;re tame by free app standards and I think I only saw ads for the pro version and the developer&#39;s other apps. It has significantly more extra keyboard keys than iSH, including some like delete, home, and end that I really feel the absence of in other apps now. Some other features it has that iSH doesn&#39;t include multiplexing support, password integration, and terminal persistence support. It even has a command snippets feature that I completely forgot about until going back into the settings looking for other things to praise while writing this. All of that had been very easy to use. Shellfish&#39;s development seems to be mostly a solo effort, so it even feels good to support it. With my server near seamlessly integrated with the Files app by Shellfish, I could treat files on my server just as though they were stored on my phone, without the hassle of manually copying them back and forth.&lt;/p&gt;
&lt;p&gt;So now I can address the thing I&#39;ve teased a few times: what I&#39;m actually using to write this post. For my first two posts, I did the bulk of the composition in &lt;a href=&quot;https://anytype.io/&quot;&gt;Anytype&lt;/a&gt;. I really like being able to put all the information I work with in one place. At least in theory, Anytype is that one place. Unfortunately, Anytype is in beta, so I had to pay an early adopter tax in the form of time spent working around underdeveloped features. Also, Anytype&#39;s mobile app is behind the desktop version in terms of features, so that further limits what Anytype can do on my phone. For my first post, I did most of the writing in Anytype and pasted the text into VS Code on my desktop to add markdown formatting. That made me comfortable enough with markdown to add formatting characters as I wrote, so I tried that when composing my second post. I quickly discovered that Anytype automatically processes markdown formatting characters as you input them and adds the respective formatting while removing the characters. That sounds helpful for somebody who&#39;s working on a more finished product, but it&#39;s the opposite for me trying to write actual markdown. Beta being beta, I couldn&#39;t turn that feature off, so I found a workaround: I used alternate characters that were available on the iPhone keyboard, that I was sure would never naturally appear in my writing, in place of the proper markdown formatting characters. Then I wrote a basic Python script to find and replace those characters in case I needed to do the same for more posts later.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My quick and dirty Python script&lt;/summary&gt;
&lt;p&gt;I put a little extra work in to encapsulate the character replacements so I could easily modify them later, but I doubt I&#39;m coming back to this.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;
    A script for cleaning up text copy-pasted from Anytype
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add_backticks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;atstr&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; atstr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replace&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;•&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;`&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add_underscores&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;atstr&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; atstr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replace&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;¥&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    out_str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sample_copy.txt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; encoding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; line &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            line &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; add_backticks&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            line &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; add_underscores&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            out_str &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; line

    &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;atclean.txt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; encoding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;write&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;out_str&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;The other text editor I used to use was &lt;a href=&quot;https://koderapp.com/&quot;&gt;Koder&lt;/a&gt;, since coding in Anytype is completely out of the question right now. It was the best-sounding free code editing app I found, even boasting remote connectivity features. I used it to test every option I found for working from my phone, and as of a few days ago, I thought it would continue to serve as my code editor. There had been plenty of small issues I had dismissed though. It wasn&#39;t nearly as bad as Buffer was for me, and it was free, so I think that made me cut it some slack. There were a few things I tried that it seemed incapable of doing, and it would crash or freeze instead of telling me no.&lt;/p&gt;
&lt;p&gt;The first crash I remember happened when I was trying to sync Koder with a remote folder. It had no trouble connecting to my server and uploading and downloading, so when I found its &amp;quot;sync with ftp&amp;quot; option, I thought that was it. But it crashed every time I tried to sync, no matter the configuration. I had only thought of it as an editor, so I wasn&#39;t very disappointed. Some other things caused instability, but they never got in the way of editing. Then I realized that there was no way to manage the list of recent files Koder maintains, so that feature would become useless once that list was long enough. Those little annoyances kept piling up, but I could still edit code much more easily than in Apple&#39;s Notes or Anytype, so I didn&#39;t make a change. Finally, with remote development on my phone underway at last, I ran into something I needed a text editor to do that Koder couldn&#39;t do: I needed to edit a &lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; template file. I found out that Koder just refuses to open files with extensions it doesn&#39;t recognize, and even certain files with no extension at all, inexplicably.&lt;/p&gt;
&lt;p&gt;So at last I&#39;ll reveal that I&#39;m writing this post using Textastic. I started with Anytype out of habit, but the first time I tried to add markdown formatting characters, the familiar frustration reminded me that I had bought Textastic a few hours earlier to edit that Nunjucks template. Textastic sounded great when I was comparing it to Buffer, and I had solved being able to edit remote files. So far, using Textastic has been a breeze. I just added a couple of directories from my server as external folders, and everything has worked perfectly. It can do its own SFTP connections, but Shellfish makes that unnecessary for the most part.  It&#39;s no substitute for multiple monitors, a mouse, and a full keyboard, but for working on an iPhone, I&#39;ve found next to nothing to complain about. It seems to be #1 in developer tools for a reason, so sometimes you do get what you pay for. However, I don&#39;t regret the time I spent trying to find free alternatives. Sure I&#39;d have gotten back down to business faster if I&#39;d spent the money right away, but I&#39;d have learned a lot less and I&#39;d have significantly less to say.&lt;/p&gt;
&lt;h3 id=&quot;going-forward-with-open-source&quot;&gt;Going forward with open source&lt;/h3&gt;
&lt;p&gt;For the time being, I don&#39;t need my desktop PC for anything. I still have to use my Windows laptop when I need Firefox&#39;s developer tools, since I haven&#39;t found a way to access something similar on my iPhone without connecting it to the actual Mac I don&#39;t have. I&#39;m more free from Microsoft and Google than I&#39;ve ever been since I started using their products, but I still have work to do on the Apple front. I&#39;m glad I managed to make my humble iPhone SE work for me for the time being, but planned obsolescence will come for it eventually. When it does, I&#39;m getting a phone that lets me choose its operating system. I came across &lt;a href=&quot;https://ubuntu-touch.io/en_GB/&quot;&gt;Ubuntu Touch&lt;/a&gt; the other day, so maybe I&#39;ll try that.&lt;/p&gt;
&lt;p&gt;I don&#39;t think I&#39;m uninstalling Windows any time soon. I&#39;m sure I&#39;ll want to play video games on a reasonably sized screen using a nice keyboard and mouse in the future, and I expect Windows will remain my best option for doing so for a while. I am planning to also install Ubuntu Desktop when I&#39;m capable of working at a desk again. And then I want to try working on my various projects using only open source software. It might take more effort to set everything up, but now I think the freedom and control will be worth it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Things I Learned About Bash Scripting Last Week</title>
    <link href="https://web.heckinchonkeires.me/blog/bash-scripting/" />
    <updated>2024-11-15T00:00:00Z</updated>
    <id>https://web.heckinchonkeires.me/blog/bash-scripting/</id>
    <content type="html">&lt;h2 id=&quot;learning-a-lot-through-unnecessary-automation&quot;&gt;&lt;strong&gt;Learning a lot through unnecessary automation&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This post recounts how I learned enough about Bash scripting to automate activating and deactivating Python virtual environments created by the &lt;code&gt;venv&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;As I’ve become more comfortable with the command line over the years, I’ve learned a little about shell scripting here and there. I was always reluctant to really get into it because, being too young to remember a time before GUIs, it seemed archaic to me. My earliest memories of playing video games involve launching them from a command line though. Lately, while I’m still a little reluctant, I’ve been finding more and more reasons to learn about it.&lt;/p&gt;
&lt;p&gt;When I was trying to debug my modifications to the &lt;code&gt;deploy-pages&lt;/code&gt; Bash script for this website, I had an incentive to try Git Bash and learn a little bit more about Bash scripting. The first thing I needed was a way to save the directory the script was executed from. I just went with the first search result for “bash save current directory.”&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My thanks to &lt;code&gt;&amp;lt;user&amp;gt;&lt;/code&gt; on &lt;code&gt;&amp;lt;well-known QA service&amp;gt;&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
I wanted to show what I found, link to where I found it, and credit the person who posted it, but I’m concerned that doing so could be outside of my “personal, non-commercial use.” I expect the risk of legal action is quite low, but I prefer to avoid it even so.
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# imagine a tidy one-liner that saves the path to the current working directory&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;Then I ran into a persistent Codeberg bug that frequently forces you to re-run &lt;code&gt;git push&lt;/code&gt;. If I was writing a JavaScript or Python script, I would have known exactly how to catch the error and run &lt;code&gt;git push&lt;/code&gt; again. So I went looking for the Bash equivalent of a &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; block.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My error handling for the Codeberg authentication bug&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote_branch&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$?&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Codeberg authentication failed. Retrying.&quot;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote_branch&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$?&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Error: Retry failed. Bother Codeberg about it.&quot;&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;I picked up the structure of a Bash script &lt;code&gt;if&lt;/code&gt; statement and that the &lt;code&gt;$var&lt;/code&gt; syntax was used to access the value of a variable from reading the original &lt;code&gt;deploy-pages&lt;/code&gt; script. What was new to me in the example I found was the &lt;code&gt;[ $? -ne 0 ]&lt;/code&gt; part. I learned that &lt;code&gt;?&lt;/code&gt; is a special bash variable that holds the exit status of the last program, and that any value besides zero meant an error had occurred. &lt;code&gt;-ne 0&lt;/code&gt; meaning &amp;quot;not equal to zero&amp;quot; made sense, but the syntax still felt strange. (&lt;em&gt;I have since learned that &lt;code&gt;[  ]&lt;/code&gt; is an alias for the &lt;code&gt;test&lt;/code&gt; program, so &lt;code&gt;-ne&lt;/code&gt; looking like a command line option and not a typical operator makes sense.&lt;/em&gt;) That script necessitated me learning more Bash script syntax, but as usual I only learned enough to get the job done. It did get me to set Git Bash as my default terminal though, and that set me on the path to finally attaining a working knowledge of Bash scripting.&lt;/p&gt;
&lt;p&gt;Last week I was setting up everything I needed to work on my &lt;a href=&quot;https://codeberg.org/Heckinchonkeires/pyanyblock&quot;&gt;Anytype Python module&lt;/a&gt;. (&lt;em&gt;I’ll write a post specifically about that project once I have enough done to write about.&lt;/em&gt;) Since my goal with that project is to make something other people can use, I’m doing my best to learn and apply best practices for Python module development. Part of that is setting up a virtual environment for the project. The &lt;code&gt;venv&lt;/code&gt; module bundled with the current version of Python makes doing so almost seamless, but I still had to type &lt;code&gt;python -m pip&lt;/code&gt; every time I wanted to use pip. I also didn’t like that I had to run the &lt;code&gt;venv&lt;/code&gt; &lt;code&gt;activate&lt;/code&gt; script every time I wanted to work in the virtual environment.  I was already using Git Bash, and my recent experience modifying a Bash script gave me the confidence that I could figure out how to automate away those annoyances.&lt;/p&gt;
&lt;p&gt;Command aliasing was quick and easy. I just needed a &lt;code&gt;.bash_profile&lt;/code&gt; file in my home directory with the line &lt;code&gt;alias pip=&#39;python -m pip&#39;&lt;/code&gt;. That worked as soon as I launched a new terminal, and it taught me that &lt;code&gt;.bash_profile&lt;/code&gt; is where I should put things for setting up the shell. (&lt;em&gt;At the time of editing this, I&#39;ve improved my Unix knowledge substantially, and now know that &lt;code&gt;.bashrc&lt;/code&gt; is the better place for aliases. That&#39;s a future post.&lt;/em&gt;) Automatically managing Python virtual environments ended up taking the rest of the day. Though I feel that particular diversion wasn’t a waste of time. At some point in the preceding couple days, I read that it was necessary to add &lt;code&gt;export PROMPT_COMMAND=&#39;history -a&#39;&lt;/code&gt; to a &lt;code&gt;.bashrc&lt;/code&gt; file in your home directory so that VS Code could save the command history of Git Bash properly. In setting up my Python project, I then had cause to learn that &lt;code&gt;PROMPT_COMMAND&lt;/code&gt; is a shell variable where you can set commands you want to run every time after the shell finishes executing the commands you entered. Then the solution for activating a Python virtual environment seemed straightforward: add an &lt;code&gt;activate_pyvenv&lt;/code&gt; script to &lt;code&gt;PROMPT_COMMAND&lt;/code&gt; to check for the &lt;code&gt;venv&lt;/code&gt; &lt;code&gt;activate&lt;/code&gt; script and run it if it’s there.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My first attempt at &lt;code&gt;activate_pyvenv.sh&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; ./Scripts/activate &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; ./Scripts/activate
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;It seemed to work at first, but I&#39;ll get into the problems I encountered later in this post. After I tested it, some quirk of VS Code made it so that the nice colour coded Git Bash prompt that updated automatically with the current directory stopped updating.
So I decided it was time to learn how to customize a Bash prompt myself. I skimmed a tutorial or two, but I wanted to really understand my options, so I ended up on the Bash Reference Manual page for &lt;a href=&quot;https://www.gnu.org/software/bash/manual/html_node/Controlling-the-Prompt.html&quot;&gt;controlling the prompt&lt;/a&gt;. I patterned my custom prompt on the original Git Bash one, with some additions that appealed to me. My first pass looked something like this:&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://web.heckinchonkeires.me/img/tB9VpQsM4y-750.avif 750w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://web.heckinchonkeires.me/img/tB9VpQsM4y-750.webp 750w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://web.heckinchonkeires.me/img/tB9VpQsM4y-750.jpeg&quot; alt=&quot;&amp;quot;-bash@localhost 2024-11-09 18:47 /home/heck/projects/website:_ fully described in the following figcaption&amp;quot;&quot; width=&quot;750&quot; height=&quot;71&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;A computer terminal prompt. White text on a black background across two lines. The text consists of, in order and separated by spaces, the shell name (bash) and host name (localhost) with an at symbol between them, the date 2024-11-09, the time 18:47, and the full directory path /home/heck/projects/website followed by a colon and an underline-style cursor. The text overflows  onto a second line in the middle of the word projects.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;The corresponding value of &lt;code&gt;PS1&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;s@&#92;h &#92;D{%F} &#92;D{%R} &#92;w:&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’m reconstructing these from memory in &lt;a href=&quot;https://ish.app/&quot;&gt;iSH&lt;/a&gt; as I didn’t have the foresight to document all my iterations.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;That prompt had the information I wanted, but it was a little plain, and the full directory path felt like too much. Plus, the cursor being so close to the prompt bothered me. So over a few  more iterations, I replaced the spaces with vertical lines, set &lt;code&gt;PROMPT_DIRTRIM&lt;/code&gt; to 2 in &lt;code&gt;.bash_profile&lt;/code&gt;, added a newline and the &lt;code&gt;&#92;$&lt;/code&gt; special character, and went back to &lt;a href=&quot;https://www.linuxscrew.com/bash-profile&quot;&gt;this tutorial&lt;/a&gt; I had skimmed earlier to figure out the colours. This is what my prompt looks like today:&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://web.heckinchonkeires.me/img/sFlq2B4WBJ-750.avif 750w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://web.heckinchonkeires.me/img/sFlq2B4WBJ-750.webp 750w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://web.heckinchonkeires.me/img/sFlq2B4WBJ-750.jpeg&quot; alt=&quot;&amp;quot;-bash@localhost|2024-11-09|19:14|.../projects/website/ $ _ fully described in the following figcaption&amp;quot;&quot; width=&quot;750&quot; height=&quot;103&quot;&gt;&lt;/picture&gt;&lt;figcaption&gt;A computer terminal prompt. Text in multiple colours on a black background across three lines. First, in order and separated by white vertical lines, the shell name (bash) and host name (localhost) with an at symbol between them in dark green, the date 2024-11-09 and the time 19:14 in purple, the partial directory path /projects/website abbreviated with an ellipsis in yellow. The yellow directory path overflows onto the second line part way through the word website. On the last line, a dollar sign and an underline-style cursor in white are separated by a space.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My current value of &lt;code&gt;PS1&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[0;32;40m&#92;s@&#92;h&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[m|&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[7;30;45m&#92;D{%F}&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[m|&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[7;30;45m&#92;D{%R}&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[m|&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[7;30;43m&#92;w&lt;span class=&quot;token entity&quot; title=&quot;&#92;e&quot;&gt;&#92;e&lt;/span&gt;[m&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;&#92;$ &quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;With a prompt I was happy with, I returned to figuring out how to manage Python virtual environments with a Bash script. I quickly realized that checking for the &lt;code&gt;activate&lt;/code&gt; script itself was not the best way to detect a Python virtual environment directory. &lt;code&gt;./Scripts/activate&lt;/code&gt; seemed like a pretty generic sounding path that could easily exist outside the context of the &lt;code&gt;venv&lt;/code&gt; module. Now &lt;code&gt;pyvenv.cfg&lt;/code&gt; was nice and specific, and (I think) should exist in the root directory of every &lt;code&gt;venv&lt;/code&gt; environment. I found a solid way of detecting when I was in the right place to run the &lt;code&gt;activate&lt;/code&gt; script, but after some testing I realized I needed to do a whole lot more.&lt;/p&gt;
&lt;p&gt;At that point, my &lt;code&gt;activate_pyvenv&lt;/code&gt; script ran the corresponding &lt;code&gt;activate&lt;/code&gt; script every time I moved to a &lt;code&gt;venv&lt;/code&gt; root directory. So if I navigated to a subdirectory and back, it would run the script again. I could have lived with that being the only problem. But then I started installing modules and noticed they weren’t always ending up where I expected. I realized that, in addition to automatically activating the virtual environment, I needed to automatically deactivate it when I left the &lt;code&gt;venv&lt;/code&gt; root directory.
The first step I took towards solving that problem was writing a script that simply detected whether, after the shell finished executing, it had gone up, down, or stayed in the same place in the directory hierarchy. I found a common Bash pattern for detecting if a string was a prefix of another using a &lt;code&gt;case&lt;/code&gt; statement. Then I tried comparing the shell environment variables &lt;code&gt;PWD&lt;/code&gt; and &lt;code&gt;OLDPWD&lt;/code&gt;, but &lt;code&gt;OLDPWD&lt;/code&gt; wasn’t changing the way I expected it to, so that was insufficient. A new variable, &lt;code&gt;VENV_PREV_DIR&lt;/code&gt;, that I updated to &lt;code&gt;$PWD&lt;/code&gt; at the end of the script had the behaviour I was looking for.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My first step towards solving the problem: &lt;code&gt;detect_cd.sh&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$VENV_PREV_DIR&lt;/span&gt;&quot;&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$VENV_PREV_DIR&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
            &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; down a level
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; same level
        &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    *&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; up a level
    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;VENV_PREV_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;I was sure that the only time I would want to run the &lt;code&gt;venv&lt;/code&gt; &lt;code&gt;activate&lt;/code&gt; script was when I navigated down into a directory containing a &lt;code&gt;pyvenv.cfg&lt;/code&gt; file and a virtual environment wasn’t already active. I decided not to waste time figuring out the “correct” way to tell if a virtual environment was already active, so I simply initialized a flag called &lt;code&gt;PYTHON_VENV_ACTIVE&lt;/code&gt; to zero in &lt;code&gt;.bash_profile&lt;/code&gt; and used that.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My first pass at &lt;code&gt;activate_pyvenv.sh&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$VENV_PREV_DIR&lt;/span&gt;&quot;&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$VENV_PREV_DIR&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; ./pyvenv.cfg &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;((&lt;/span&gt; &quot;$PYTHON_VENV_ACTIVE&quot; &lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
					&lt;span class=&quot;token assign-left variable&quot;&gt;PYTHON_VENV_ACTIVE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
					&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; activating python venv
					&lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; ./Scripts/activate
				&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
				&lt;span class=&quot;token comment&quot;&gt;# placeholder&lt;/span&gt;
            &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# do nothing&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    *&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token assign-left variable&quot;&gt;PYTHON_VENV_ACTIVE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
			&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; deactivating python venv
			deactivate
    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;VENV_PREV_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;I was getting closer. The logic for determining when to activate worked well, but I was deactivating every time I went up in the directory hierarchy. I needed to detect when I left the actual &lt;code&gt;venv&lt;/code&gt; directory. Saving the current directory to &lt;code&gt;VENV_DIR&lt;/code&gt;, a new shell variable, when activating a virtual environment came to me right away. Yet I struggled to effectively determine when I had left that directory. I tried &lt;code&gt;if [[ $VENV_PREV_DIR = $VENV_DIR ]]&lt;/code&gt; when going up, but that only worked when moving up out of exactly the &lt;code&gt;venv&lt;/code&gt; root directory, and not when moving up out of that directory from a subdirectory. Then I came to an understanding: the core logic of the script was figuring out if a new directory was a subdirectory of the previous one, and I needed to know if a new directory wasn’t a subdirectory of the saved &lt;code&gt;VENV_DIR&lt;/code&gt;. So I nested another &lt;code&gt;case&lt;/code&gt; statement in the “moving up” condition and the script was done but for some minor polishing.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;The current version of &lt;code&gt;activate_pyvenv.sh&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# manages automatically activating and deactivating Python virtual environments created by the venv module&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# this case statement checks if the current directory is a subdirectory of&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# the last one visited by the shell&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$VENV_PREV_DIR&lt;/span&gt;&quot;&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$VENV_PREV_DIR&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# the shell just started or we&#39;ve gone down in the directory hierarchy&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; ./pyvenv.cfg &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;((&lt;/span&gt; &quot;$PYTHON_VENV_ACTIVE&quot; &lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;# we&#39;re in a python virtual environment and it needs activating&lt;/span&gt;
                &lt;span class=&quot;token assign-left variable&quot;&gt;PYTHON_VENV_ACTIVE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;# save the venv directory so we can tell when we&#39;ve left&lt;/span&gt;
                &lt;span class=&quot;token assign-left variable&quot;&gt;VENV_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt;
                &lt;span class=&quot;token assign-left variable&quot;&gt;short_venv_dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${VENV_DIR&lt;span class=&quot;token operator&quot;&gt;##&lt;/span&gt;*&#39;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&#39;}&lt;/span&gt;
                &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; activating python venv &lt;span class=&quot;token variable&quot;&gt;$short_venv_dir&lt;/span&gt;
                &lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; ./Scripts/activate
            &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# placeholder in case I want to do something when I don&#39;t&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# change directories&lt;/span&gt;
            &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# do nothing&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    *&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# we&#39;ve gone up in the directory hierarchy&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$PWD&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$VENV_DIR&lt;/span&gt;&quot;&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;# we&#39;re still in a subdirectory of VENV_DIR&lt;/span&gt;
                &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# so do nothing&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            *&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;# we&#39;ve escaped the virtual environment&lt;/span&gt;
                &lt;span class=&quot;token assign-left variable&quot;&gt;PYTHON_VENV_ACTIVE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;token assign-left variable&quot;&gt;short_venv_dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${VENV_DIR&lt;span class=&quot;token operator&quot;&gt;##&lt;/span&gt;*&#39;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&#39;}&lt;/span&gt;
                &lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; VENV_DIR
                &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; deactivating python venv &lt;span class=&quot;token variable&quot;&gt;$short_venv_dir&lt;/span&gt;
                deactivate
            &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; working_dir
&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; short_dir
&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; short_venv_dir
&lt;span class=&quot;token assign-left variable&quot;&gt;VENV_PREV_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tried to save a couple lines of code by omitting the variables for the short directory names, but I quickly gave up on figuring out the correct syntax.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;At the time I finished the script, I was sure there were some things I had done clumsily that could be improved by somebody with more Bash experience. I also acknowledged to myself that it wouldn’t work if I navigated straight into a subdirectory of a &lt;code&gt;venv&lt;/code&gt; directory. But, as the saying goes: “perfect is the enemy of done.” I thought I would have to figure out how to check every directory above the current one for a &lt;code&gt;pyvenv.cfg&lt;/code&gt; file, and that it would take some time. I added the script to my list of things to revisit if I run out of project ideas. It worked well enough, so I was happy to move on and get some “real work” done.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How I Built This Site</title>
    <link href="https://web.heckinchonkeires.me/blog/firstpost/" />
    <updated>2024-11-06T00:00:00Z</updated>
    <id>https://web.heckinchonkeires.me/blog/firstpost/</id>
    <content type="html">&lt;h2 id=&quot;from-a-domain-and-an-idea-to-a-functional-personal-blog&quot;&gt;&lt;strong&gt;From a domain and an idea to a functional personal blog&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This is my first proper post on web.heckinchonkeires.me. It’s the story of how this site came to be, and the things I learned and mistakes I made along the way. I think it will serve as a helpful explanation of the hows and whys of this website, as well as a good example of the kind of writing I intend to post here.&lt;/p&gt;
&lt;p&gt;I’ve had a personal website on my mind for a long time. I experimented with some free web application hosts in the past, but their offerings didn&#39;t seem appropriate for what I wanted to do here. I finally bought heckinchonkeires.me this year, and I started doing some work on turning an old PC into a web server. I still intend to finish that project, but it dawned on me that it was going to take a while. I’m trying to start a career as a software developer (&lt;a href=&quot;mailto:dev@heckinchonkeires.me?subject=Job%20Offer&quot;&gt;hire me please&lt;/a&gt;), and while I expect knowing how to operate a small web server would be an asset, I need to prioritize. If learning all that is going to be of any use to my career, I need a way to show potential employers what I’ve learned. So that’s where this website is starting from; a place to publicly document my software projects. In addition to benefiting my career, this site serves as an incentive to document my work as I go. And turning my collection of comments, notes, and discarded test scripts into something comprehensible to somebody else helps me better understand and remember what I learned.&lt;/p&gt;
&lt;p&gt;I had a domain and a decent idea of what I wanted to do with it. Before I found my current hosting solution, I had done some research into free blog options, but they all seemed to be aimed at non-coders. Wordpress.com and the like are fine if you just want an easy way to get words onto the internet, but they limit what somebody with web development skills can do with them. Last week I was thinking about changing git hosts, and &lt;a href=&quot;https://codeberg.org&quot;&gt;Codeberg&lt;/a&gt; came to mind. I looked around their website and I came across &lt;a href=&quot;https://codeberg.page&quot;&gt;codeberg.page&lt;/a&gt;. That page had three steps I understood, it was free, and they even allowed custom domains. I was already convinced to use Codeberg as a Git host, and Codeberg Pages also solved my web hosting problem. It only hosts static sites, so I can&#39;t do everything I want to do with a personal website, but it gives me more than enough control for my immediate goals. For the time being, I just want to host text content somewhere on the open web, and now I can do that and more.&lt;/p&gt;
&lt;p&gt;Getting anything at all on the internet was solved, but I still needed a way to make something presentable in a timely manner. I came across &lt;a href=&quot;https://johnnyjayjay.codeberg.page/pages-tutorial/&quot;&gt;this tutorial&lt;/a&gt;, and it was very helpful. Not only did following it get a custom web page online quickly, it made me realize that what I needed was a static site generator. I chose &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; because the author used it in the tutorial, and it turned out to be a great choice. I also really appreciate the included &lt;code&gt;deploy-pages&lt;/code&gt; shell script, and I’m still using a modified version of it.&lt;/p&gt;
&lt;p&gt;With a domain, a web host, and a site generator, I still needed to put a site together. I&#39;m sure I could have built a whole blog from scratch using Eleventy, but it would have taken significantly longer and probably not looked great. So I went looking for a better starting point and quickly found the &lt;a href=&quot;https://github.com/11ty/eleventy-base-blog&quot;&gt;Eleventy Base Blog&lt;/a&gt; starter project linked in the Eleventy documentation. It was perfect. That project would give me a functional blog in little time, and from what I had learned about Eleventy, it seemed like it would be easy to build on that base. On top of that, the Base Blog repository is well documented, so I feel confident that figuring out what I need to change when the time comes won&#39;t be a problem.&lt;/p&gt;
&lt;p&gt;So I learned my way around the starter project and got a copy of it online under my own domain. At that point, I decided I wanted to limit the Git repository for the actual website to the rendered content and keep the generator itself in a separate repository. Changing the output directory for Eleventy was easy. I also wanted to keep using the &lt;code&gt;deploy-pages&lt;/code&gt; script to save some typing every time I updated the site, but it didn&#39;t quite work for my new use case. I didn’t know much about shell scripting at the time; I understood where I needed to make the script change directories and why that was necessary, but parts of it that were beyond me kept causing errors. I knew enough to tell which parts were essential, so I removed the error-prone sections until it worked. My major addition was some error handling logic for a Codeberg bug that requires re-running &lt;code&gt;git push&lt;/code&gt;. I think the script is still overcomplicated for my use case, though it allows for some flexibility should I change things.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My addition to &lt;code&gt;deploy-pages.sh&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote_branch&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$?&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Codeberg authentication failed. Retrying.&quot;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$remote_branch&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$?&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Error: Retry failed. Bother Codeberg about it.&quot;&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;Once I had things organized so that I could easily push any updates I made to the live site, I started working on building the starter project into something I would feel comfortable calling my own. I started in the root directory, deleting some files that were there to facilitate services I didn’t plan on using. I updated the package.json file with details about myself and the project. I made sure to write my own &lt;code&gt;README&lt;/code&gt;, and then I turned my attention to the &lt;code&gt;LICENSE&lt;/code&gt; file. I had done a good amount of reading about copyright and licenses and I settled on using &lt;a href=&quot;https://creativecommons.org/publicdomain/zero/1.0/?ref=chooser-v1&quot;&gt;CC0&lt;/a&gt; for my writing. My research hadn’t focused on software, so I wasn’t sure what my options were regarding licensing my modifications to code with an existing license. I searched for information about this more specific case, though I could have just stopped at reading the &lt;a href=&quot;https://opensource.org/license/MIT&quot;&gt;MIT license&lt;/a&gt; included in the starter project. In short: I can do whatever I want as long as I include the same license in the project. That precluded me from using CC0 for the site generator project, but I found the MIT license permissive enough. So I’m going to license my modifications using MIT. Also, while not required by that license, I decided I would keep the original author’s copyright in the license file as a sign of my respect and appreciation for their work. I added the author of the deploy-pages script to the license file as well, and I intend to do the same for as much of the non-library code I use in my projects as is feasible.&lt;/p&gt;
&lt;p&gt;Having made my version of the Eleventy Base Blog repository ready to publish under my own (pseudo)name, I started working on the content for the site. All that thinking about copyright made me prioritize adding a CC0 declaration to the site’s footer, and I added my explanation of CC0 to the about page. &lt;a href=&quot;https://creativecommons.org/&quot;&gt;Creative Commons&lt;/a&gt;’ template for a CC0 footer included links to their CC0 page, and I elected to put one on my about page as well. Everything worked, but I didn’t like that the external links opened in the same tab. At the time I thought opening external links in a new tab by default was a good thing to do, but I&#39;m reconsidering that now. I looked up the HTML attributes I needed to add, then immediately realized there was no way I was going to manually add those attributes to every external link on the site. I’m a programmer after all, so I’m always up for automating things, even if it might not be necessary. I was sure Eleventy would make applying the attributes I wanted easier, so I went back to its documentation. &lt;a href=&quot;https://www.11ty.dev/docs/transforms/&quot;&gt;Transforms&lt;/a&gt; sounded like what I was looking for: I needed to automatically transform external links after all. At the time, I thought Eleventy made the content of each output page available as HTML text strings (or at least something similar) and I was already familiar with &lt;a href=&quot;https://www.npmjs.com/package/node-html-parser&quot;&gt;Fast HTML Parser&lt;/a&gt;, a JavaScript library for working with HTML. My misunderstanding was that the content Eleventy provides to transforms is the rendered output before it’s written to the output directory, or at least something much closer to that than what it actually is. I wrote my transform function to parse the content provided by Eleventy and add the necessary attributes to all external links. I got that function working relatively quickly, but it only worked on the links in the footer.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;My first working transform function from &lt;code&gt;eleventy.config.js&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-javascript&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTransform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;external-link&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outputPath &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; links &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementsByTagName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// make sure we found links to transform&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;links &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; link &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// avoid an error if the link somehow doesn&#39;t have an href&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lhref &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;href&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// all external links should start with http&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lhref&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// avoid overwriting links with existing attributes&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                            link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_blank&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                            link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;noopener noreferrer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// no links found, return original content&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// make sure to still return the original content if something goes wrong&lt;/span&gt;
            console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;That was when I realized that what I was actually transforming was the content of the template and layout files before Eleventy starts rendering them. My transform only worked for the footer because it was defined in a Nunjucks template file, which partly used HTML syntax. It was getting late, so I left myself some sections in the transform function to fill in for handling external links in non-HTML formatted files. The next day I went completely off the rails. I spent most of it trying to write a general JavaScript function that could identify and modify links in arbitrary strings. The &lt;a href=&quot;https://linkify.js.org/&quot;&gt;Linkify&lt;/a&gt; library helped, but the most it could do in this case was pick out every URL in a string. So Linkify saved me the identification part, but what it gave me was a list of URL strings and their start and end indices in the main content string. The problem perceived was that it found &lt;strong&gt;every&lt;/strong&gt; URL in the main string. Including the ones inside comments. I now realize it would have been fine to skip filtering out URLs in comments. Comments don’t make it into Eleventy’s final render (by default at least), so there would be no harm in processing the links inside them. But. I identified a problem and I thought I knew how to solve it. I enjoy solving problems, especially when I don’t have a deadline, so I went for it. By the end of the day, I had written what I later realized was my own implementation of JavaScript’s &lt;code&gt;String.lastIndexOf()&lt;/code&gt; method (despite consulting the full list of JavaScript string methods earlier). I could generate a list of all (non-commented) URL strings and I thought I had almost figured out how to find out if they were inside &lt;a&gt; tags. I realized I was struggling, so I gave up and went to bed.&lt;/a&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;What&#39;s left of my first attempt to filter out commented strings&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-javascript&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Returns an array of the start and end indices of every instance of&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// str in content that is not within a comment.&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Currently works for xml, nunjucks, and javascript comments.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findUncommented&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;str&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; regex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;str&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;gd&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; found_array &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; search_array&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; last_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Regex.exec() provides a handy way to find all the instances of str&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;search_array &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; regex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; start_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; search_array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;indices&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; end_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; search_array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;indices&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; before_url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;last_index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; start_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; after_url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; first_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; first_delim_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; last_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; last_delim_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; after_url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// STARTDELIMS and ENDDELIMS are constant arrays containing comment&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// delimiters for xml, javascript, and nunjucks defined earlier in&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// the file. They keep breaking my markdown, so I&#39;m not including them&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// here.&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; delim &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;STARTDELIMS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dl_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; before_url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delim&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// we want the index of the delimiter closest to str&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dl_index &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; dl_index &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; first_delim_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                first_delim_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dl_index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                first_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; delim&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// special case of single-line comments delimited by newlines&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;first_delim &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#92;&#92;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            last_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// the first newline after str should always be the&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// last delimiter in this case&lt;/span&gt;
            last_delim_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; after_url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// similar to start delimiters&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; delim &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENDDELIMS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dl_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; after_url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delim&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dl_index &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; dl_index &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; last_delim_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    last_delim_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dl_index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    last_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; delim&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;first_delim &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;last_delim&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            found_array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            found_array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; found_array&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Based on what I learned to arrive at &lt;code&gt;isCommented()&lt;/code&gt; below, this doesn&#39;t actually work. I thought it did at one point though.&lt;/p&gt;
&lt;/details&gt;
&lt;br&gt;
&lt;details&gt;
&lt;summary&gt;The most functional thing I produced&lt;/summary&gt;
&lt;br&gt;
I remembered &lt;code&gt;String.lastIndexOf()&lt;/code&gt; existed for this one.
&lt;pre class=&quot;language-javascript&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Returns true if the substring of str starting at start_index and&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ending at end_index is within a pair of comment delimiters.&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Currently works for xml, nunjucks, and javascript comments.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isCommented&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;start_index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end_index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; before &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; start_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; after &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; first_dl_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; first_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Javascript has comments that are partial delimited by newlines,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// so we have to keep a special index for those.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; nl_dl_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// STARTDELIMS and ENDDELIMS are constant arrays containing comment&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// delimiters for xml, javascript, and nunjucks defined earlier in&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// the file. They keep breaking my markdown, so I&#39;m not including them&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// here.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; delim &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;STARTDELIMS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dl_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; before&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delim&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delim &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;//&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; dl_index &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; nl_dl_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            nl_dl_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dl_index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dl_index &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; first_dl_index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            first_dl_index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dl_index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            first_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; delim&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; matched_delim&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Pretty hacky way of avoiding finding the &quot;//&quot; in a url&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nl_dl_index &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; first_dl_index &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; before&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;nl_dl_index &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// I don&#39;t think this ever happens with the content I&#39;m using this with.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// To generalize this function further, I&#39;d need to figure out how to&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// actually tell when &quot;//&quot; delimits a comment.&lt;/span&gt;
        first_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;//&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        matched_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Matching delimiters should always work at this point.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Better to be safe than sorry though.&lt;/span&gt;
        matched_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENDDELIMS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;STARTDELIMS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;first_delim&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; last_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;after&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matched_delim&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        last_delim &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; matched_delim&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;first_delim &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; last_delim&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This worked great, but it still left me needing to find and modify the HTML tags&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;The next morning came with some critical clarity: I realized why transforms were the wrong tool for what I was trying to do. Over breakfast I returned to the Eleventy documentation, looking for some way to insert my code further into the rendering process. I found the &lt;a href=&quot;https://www.11ty.dev/docs/events/&quot;&gt;events&lt;/a&gt; page and the &lt;a href=&quot;https://www.11ty.dev/docs/events/#eleventy.after&quot;&gt;&lt;code&gt;eleventy.after&lt;/code&gt;&lt;/a&gt; event. It wasn&#39;t exactly what I wanted, but it did the job. (I was hoping to avoid re-writing to the output directory. &lt;code&gt;eleventy.after&lt;/code&gt; feels like only a minor improvement over a standalone script that modifies the output content, since it makes accessing the necessary page attributes easier.) All I had to do was modify my original HTML-parsing function slightly and slip it into &lt;code&gt;eleventy.after&lt;/code&gt;. I had that working less than 15 minutes after sitting down at my desk. I&#39;m working on reining in this tendency towards creating &lt;a href=&quot;https://xyproblem.info/&quot;&gt;XY problems&lt;/a&gt; for myself. That day spent barking up the wrong tree was fun, but I’d rather I got around to working on this post a day earlier.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;The actual solution in &lt;code&gt;eleventy.config.js&lt;/code&gt;&lt;/summary&gt;
&lt;br&gt;
&lt;pre class=&quot;language-javascript&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;	&lt;span class=&quot;token comment&quot;&gt;// make all external links open in a new tab&lt;/span&gt;
	eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;eleventy.after&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; dir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; results&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; runMode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputMode &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; result &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; results&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outputPath &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
					&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; links &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementsByTagName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
					&lt;span class=&quot;token comment&quot;&gt;// make sure we found links to modify&lt;/span&gt;
					&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;links &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
						&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; link &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
							&lt;span class=&quot;token comment&quot;&gt;// avoid an error if the link somehow doesn&#39;t have an href&lt;/span&gt;
							&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lhref &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;href&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
							&lt;span class=&quot;token comment&quot;&gt;// by convention, all external links will start with http(s): or mailto:&lt;/span&gt;
                            &lt;span class=&quot;token comment&quot;&gt;// TODO: make a regex already&lt;/span&gt;
							&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lhref&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; lhref&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; lhref&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mailto:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
								&lt;span class=&quot;token comment&quot;&gt;// avoid overwriting links with existing attributes&lt;/span&gt;
								&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
									link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_blank&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
								&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
									link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;noopener noreferrer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
							&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
						&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
						fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outputPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
							&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
								console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
						&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
					&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;So I have a functional blog and I have a post on it. If you’ve made it this far, dear reader: sorry, first of all. Secondly: you might be wondering what else you can expect from this site. I have plenty of wild ideas for things to do with a personal website, but I’m prioritizing building a career, so I’m going to implement mostly less wild ones. I’m trying to specialize in tools programming, and I have a lot of learning to do in the process. (I hardly know any C++ as of posting this, for example.) So I’m going to document that process, both to aid in my understanding and create a public record of my skills for potential employers. Maybe some people on a similar path to me will also benefit from my work here, but that would be a bonus. Outside of my chosen career path, I’m still very interested in web design and passionate about digital accessibility. I want to explore the limits of what’s possible with a static site, and I want to make this website as accessible as I can. Memorizing all of &lt;a href=&quot;https://www.w3.org/WAI/standards-guidelines/wcag/&quot;&gt;WCAG&lt;/a&gt; wouldn’t be much help in making the web better for everyone if I didn‘t have the knowledge and experience to implement and go beyond those guidelines. So expect writing about things I learn and projects I work on related to tools programming and web design in the near future. I also have interests unrelated to my career that I want to write about, but such writing would belong elsewhere. If you&#39;re still here and interested, watch this space. If you&#39;re &lt;strong&gt;really&lt;/strong&gt; interested in anything I write here, you&#39;re welcome to &lt;a href=&quot;mailto:dev@heckinchonkeires.me&quot;&gt;send me an email&lt;/a&gt; about it. And if you&#39;re looking to hire a software developer, &lt;a href=&quot;mailto:dev@heckinchonkeires.me?subject=Job%20Offer&quot;&gt;I&#39;m available&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
</feed>