<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Hacker News Personal Blogs 2025 | Top 100 Blogs</title><link>https://hn-blogs.kronis.dev/feed-top100.xml</link><description>A collection of blog posts from users of Hacker News, based on RSS feeds.</description><language>en-US</language><lastBuildDate>Wed, 20 May 2026 04:03:16 GMT</lastBuildDate><generator>rfeed v1.1.1</generator><docs>https://github.com/svpino/rfeed/blob/master/README.md</docs><item><title>I canceled my book deal</title><link>https://austinhenley.com/blog/canceledbookdeal.html</link><description>&lt;a href="https://austinhenley.com/blog/canceledbookdeal.html"&gt;https://austinhenley.com/blog/canceledbookdeal.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Wed, 31 Dec 2025 22:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/canceledbookdeal.html</guid></item><item><title>2025 Year in Review</title><link>https://benovermyer.com/blog/2025/12/2025-year-in-review/</link><description>&lt;p&gt;Well then. 2025 did not go as originally planned. Not even close.&lt;/p&gt;
&lt;h2 id="a-year-of-challenges"&gt;A year of challenges&lt;/h2&gt;
&lt;p&gt;We attempted to foster a puppy in January. She was a cute little &lt;a href="https://www.wect.com/2025/02/14/pet-week-clover-freedom-bridge-animal-rescue/" rel="external"&gt;rescue pit mix named Clover&lt;/a&gt;. Unfortunately, I discovered that I'm allergic to dogs. I don't know if this is new or if it's just something I never noticed before. While it seems to be a relatively mild allergy, combined with other factors, it meant we had to stop fostering. Luckily, there was another foster family able to take Clover right away.&lt;/p&gt;
&lt;p&gt;The next event of consequence was Trump taking office. We knew it was going to be bad, but even my worst fears paled compared to what he did. In response, we decided I would attempt a different route to immigration. I started applying to as many universities in Canada and Estonia as I could.&lt;/p&gt;
&lt;p&gt;In March, one of them accepted my application: &lt;a href="https://mun.ca" rel="external"&gt;Memorial University of Newfoundland&lt;/a&gt;. So, we immediately began the planning process to move to Newfoundland. In April, we flew up to St. John's to explore the area and see if we could find a rental that would accept cats. We were shocked to discover that rentals mostly disallow pets, with no possibility of pet rent or anything like that. However, we got lucky, and found a three-bedroom house whose landlord loves cats. So, we signed the lease and started paying for two places at once. While it meant the new house would stand empty for several months before we could move in, we guaranteed that we'd have a place.&lt;/p&gt;
&lt;p&gt;It was around this time that I completed the most complex painting I've ever done. It was a painting of my sister's eleven cats, and originally meant as a Christmas gift. Still, a nice achievement, and she ended up loving it.&lt;/p&gt;




&lt;div class="dress-image"&gt;
    &lt;img alt="A painting of eleven cats" src="https://benovermyer.com/blog/2025/12/2025-year-in-review/&amp;#x2F;&amp;#x2F;benovermyer.com&amp;#x2F;processed_images&amp;#x2F;tashas-cats-2025.1b97033d255d38c4.jpg" /&gt;
    
    &lt;div class="caption"&gt;The painting of Tasha&amp;#x27;s cats&lt;/div&gt;
    
&lt;/div&gt;

&lt;p&gt;For the next few months, we watched as Trump ran roughshod over all the rules and rights that normally guide the government and began systematically destroying the United States. Then it came time to give notice at our jobs and prepare for the two-week drive it would take to get to our new home in St. John's, Newfoundland.&lt;/p&gt;
&lt;h2 id="the-trek-northward"&gt;The trek northward&lt;/h2&gt;
&lt;p&gt;That drive took us to some amazing places with great views. Under other circumstances, it would have been a wonderful experience. However, I had our two cats and a ton of possessions crammed into my Kia Seltos, and the cats &lt;em&gt;hated&lt;/em&gt; that drive. I will never put them through that again.&lt;/p&gt;




&lt;div class="dress-image"&gt;
    &lt;img alt="A photo of our cats" src="https://benovermyer.com/blog/2025/12/2025-year-in-review/&amp;#x2F;&amp;#x2F;benovermyer.com&amp;#x2F;processed_images&amp;#x2F;cats-before-trip-2025.e15ec594a8a0aff1.jpg" /&gt;
    
    &lt;div class="caption"&gt;Mojave and Kalahari a few days before we took the trip&lt;/div&gt;
    
&lt;/div&gt;

&lt;p&gt;Still, we got to our destination safely, with everyone and everything intact. We moved into our new place in mid August. The UHaul U-Box arrived more or less on time, and we got everything that we'd brought with us sorted out. The majority of our stuff is still back in Wilmington, North Carolina, though, in a big storage unit. We can't bring all that up until we get permanent resident status. That won't happen until after I've graduated and we've both spent a year living and working here.&lt;/p&gt;
&lt;p&gt;In order to retain access to our US bank accounts and a few other services, we have to retain our US phone numbers. We're using US Mobile for that purpose; it's about $15 USD per month for the two lines on the lowest service level. We have new Canadian numbers at the same time. Surprisingly, it was easy to get Canadian bank accounts and credit cards. Sarah was able to get a remote job to pay the bills, and for the first semester of university, I even got an internship that paid a reasonable amount of money.&lt;/p&gt;
&lt;h2 id="starting-grad-school"&gt;Starting grad school&lt;/h2&gt;
&lt;p&gt;My first semester was stressful. I had to reacclimate to the university life, and between three classes, an entrepreneurship program, and my internship, I was extremely busy. One of my classes, Applied Algorithms, was really difficult for me. After a ton of studying, consulting my dad, and working with ChatGPT (and, later, Gemini) to understand the concepts, I managed to finish the class with a B, but it was a struggle. If you get less than a B in a class at the graduate level, you fail the class and have to retake it. If you fail two classes (or the same class twice) this way, then you're kicked out of the program. If you're an international student, that immediately invalidates your study permit, and you are required to leave the country. So, you can understand why I was stressed.&lt;/p&gt;
&lt;p&gt;In the end, though, it worked out. I got As or Bs in all of my classes, and passed the entrepreneurship program. I finished my internship successfully, with an invitation to come back if I ever want to.&lt;/p&gt;
&lt;h2 id="so-about-those-planned-new-habits"&gt;So, about those planned new habits&lt;/h2&gt;
&lt;p&gt;With regards to the new habits I originally intended to set this year, the events of 2025 completely derailed everything. I couldn't bring my guitar with me, so the guitar habit died around about May. I was improving quickly, though, and apparently I can borrow a guitar from the local library here in St. John's, so maybe I'll pick that back up now.&lt;/p&gt;
&lt;p&gt;The painting habit never took, though I have painted more this year than any other year since art school. I'm improving at that, too, and I now have a basic painting setup in the new place. There's promise here for 2026.&lt;/p&gt;
&lt;p&gt;Alcohol... well, quitting alcohol didn't happen. While I didn't drink as much this year as previous years, I still drank way more than I planned to. This will be another thing I address in 2026.&lt;/p&gt;
&lt;p&gt;The daily walking habit never formed. However, I did start walking more in St. John's compared to Wilmington, since there are a number of &lt;a href="https://www.alltrails.com/trail/canada/newfoundland-and-labrador/quidi-vidi-lake-loop" rel="external"&gt;amazing trails&lt;/a&gt; here that are really close to our house. It should be easy to establish a walking habit going forward. I can even walk to campus in about an hour and fifteen minutes, which is ambitious but doable.&lt;/p&gt;




&lt;div class="dress-image"&gt;
    &lt;img alt="Photo of one of the trails near our house" src="https://benovermyer.com/blog/2025/12/2025-year-in-review/&amp;#x2F;&amp;#x2F;benovermyer.com&amp;#x2F;processed_images&amp;#x2F;newfoundland-trail-2025.4e8de385e92f5bd2.jpg" /&gt;
    
    &lt;div class="caption"&gt;One of the trails near our house&lt;/div&gt;
    
&lt;/div&gt;

&lt;h2 id="books-read"&gt;Books read&lt;/h2&gt;
&lt;p&gt;My boss Dwayne at Apiture introduced me to the novel series Dungeon Crawler Carl early in the year, and I adored it. I read everything that was available, and for Christmas I got the latest book in the series, so I intend to start on that early next year.&lt;/p&gt;
&lt;p&gt;I also got into the Dune novels after playing the survival MMO &lt;a href="https://duneawakening.com" rel="external"&gt;Dune: Awakening&lt;/a&gt;, and read up to Chapterhouse: Dune, which is the last Dune novel written by Frank Herbert. They were a strange and unusual series of novels. I have no idea how they could make a third Dune movie in the current series without touching on some very weird concepts from the books.&lt;/p&gt;
&lt;p&gt;Total books read this year: 20&lt;/p&gt;
&lt;h2 id="music-discovered"&gt;Music discovered&lt;/h2&gt;
&lt;p&gt;A little over a month ago, I started a new habit of discovering and buying a brand new album every Friday. I wanted to expand the collection of music that I actually own, as opposed to just streaming everything. My record player is still in storage in North Carolina along with all of my records, so I'm just buying digital albums. Still, it's led me to discover some fascinating new music that I otherwise would never have heard.&lt;/p&gt;
&lt;p&gt;I've discovered that I enjoy thrash metal and black metal. One unusual standout was &lt;a href="https://www.bloodywood.net" rel="external"&gt;Bloodywood&lt;/a&gt;, a nu metal band from New Delhi with Hindu and English lyrics. They even did a collaboration with &lt;a href="https://babymetal.com/" rel="external"&gt;Babymetal&lt;/a&gt;, a unique Japanese metal group that I used to follow years ago.&lt;/p&gt;
&lt;p&gt;Hmm. Maybe I should start an "albums discovered" page in the same vein as my "&lt;a href="https://benovermyer.com/kb/books/"&gt;books read&lt;/a&gt;" page.&lt;/p&gt;
&lt;h2 id="yoga-progress"&gt;Yoga progress&lt;/h2&gt;
&lt;p&gt;Another unexpected development this year is that I started getting into yoga. I start most mornings now with a basic routine that takes about 5 minutes. My skill level is still "absolute beginner," though. There are classes here in St. John's, but I don't have a lot of spare money for such things. I enjoy the practice, and use yoga workouts on Apple Fitness+ to learn new asanas.&lt;/p&gt;
&lt;p&gt;I am interested in the other aspects of yoga, too, and not just the stretching. The world view is worth exploring.&lt;/p&gt;
&lt;h2 id="in-closing"&gt;In closing&lt;/h2&gt;
&lt;p&gt;That's some of our adventures from 2025. I skipped mentioning a few things, but this post is already quite long enough. 2025 was a year of big changes. One thing is certain: no one will ever say 2025 was boring.&lt;/p&gt;
&lt;h2 id="previous-years"&gt;Previous Years&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2024/12/2024-year-in-review/"&gt;2024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2023/12/2023-year-in-review/"&gt;2023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/12/2022-year-in-review/"&gt;2022&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2021/12/2021-year-in-review/"&gt;2021&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2020/12/2020-year-in-review/"&gt;2020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2018/12/2018-year-in-review/"&gt;2018&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2017/01/looking-back-on-2016-and-forward-to-2017/"&gt;2016&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2016/01/2015-year-in-review/"&gt;2015&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2013/12/2013-year-in-review/"&gt;2013&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Ben Overmyer's Site</author><pubDate>Wed, 31 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/12/2025-year-in-review/</guid></item><item><title>I'm sick and tired of AI coders</title><link>https://burakku.com/blog/tired-of-ai-coders/</link><description>&lt;p&gt;&lt;img alt="Please don't ask me about our software" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;This has been a weird year for me, with the biggest thing being that I suddenly found myself without a job. This was a bit of a new experience for me, since I hadn’t been unemployed ever since I graduated. Had to fill all kinds of government paperwork about it. Not exactly the happiest of circumstances to find yourself in, but I came into it from a much better starting position than many of my fellow layabouts, so I’ll refrain from crying about it too much.&lt;/p&gt;
&lt;p&gt;Now I’m back in the workforce again, still making computers do things for us. Those things are just a bit different, and I’m doing them in a different environment, but I didn’t suddenly become a bricklayer or anything. I’m still even a remote employee, so I didn’t even have to go into the office like many of my peers did. The biggest difference though? I am now in a company where my coworkers, or at least the most vocal of them, have taken the AI pill – hard. And I really don’t care for it.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;I’m a proponent of the idea that you get better at things by doing them. That’s why I have a goal of playing my guitar for at least 15 minutes every day, no matter what. I’m currently ~1700 days into this experiment, so that should have given me 425+ hours to improve my playing. According to Malcolm Gladwell, that’s over 4% of mastery. It’s also part of why I started a blog, since even if nobody were to read anything that I wrote here, I’d at least give myself a chance to improve my writing. Not that I write here that often, but I feel like it has still helped.&lt;/p&gt;
&lt;p&gt;This is one of the reasons why I’m skeptical of "agentic coding": I don’t think I will become a better developer by letting an AI model write code for me. And I’m almost certain that I won’t become a better developer by doing it like some of my coworkers are doing it. Some of them do not seem to do any kind of validation on the code beyond the fact that the unit tests (which the AI wrote) pass, the integration tests (which the AI wrote) work, and that the documentation (which the AI wrote) is accurate (as evaluated by the AI).&lt;/p&gt;
&lt;p&gt;Supposedly the easiest way of catching students using AI to cheat on their homework is simply by asking them questions about their work. The chances are that they will not be able to actually tell you what is inside whatever it is that they’ve just turned in. I can’t vouch for the veracity of this method, but I have noticed that the people who are using agents to code don’t really seem to know what’s inside the commits they’re shipping out. Presumably there are teams where AI is used in a responsible manner and people make sure to understand what they are shipping, but at least where I work, velocity seems to come before reason or understanding.&lt;/p&gt;
&lt;p&gt;I’ve seen the LinkedIn post of the tech executive praising their AI-native chief vibist, who is pushing out 250,000+ lines of code every month. That comes out to around 12,000 lines of code generated each working day. Obviously I can’t write 12,000 lines of code in a single day, but I don’t think I can review 12,000 lines of code in a day either. Is the price of our glorious AI-generated future that we just don’t look at the code that we ship?&lt;/p&gt;
&lt;p&gt;It really boggles my mind how some people can put so much trust in these language models. I don’t even trust my own code this much; I always go through my own code again before I submit it to be reviewed by another person. Either I’m the odd one here for double-checking my work, or LLMs have blinded developers into thinking they’re near-infallible 10x coders and reviewing their changes serves only to unnecessarily slow down your business. But from my experience working with a codebase that has a significant amount of AI-generated code in it, I think mistrust is the right call.&lt;/p&gt;
&lt;p&gt;The general quality of our AI-generated code is quite poor. Claude does very little in terms of adhering to DRY, it’s writing CRUD tests against completely barren databases, and if a test doesn’t pass, it might solve the issue not by changing the core business logic, but rather by changing the test until it passes. And I can't even really get into the truly important specifics, like for example what Claude is doing with access control. We probably wouldn't pass a security audit even if the audit was done by Claude itself.&lt;/p&gt;
&lt;p&gt;It feels like every day I discover a new problem that can be described as "developer prompted the ticket, didn’t check it, didn’t run it". There’s only so much code that I’ve had time to look at, or run, so I don’t even know how far the rot has spread. Most of these are in a greenfield project that has yet to land on the monitors of end-users, so the customer experience thankfully hasn’t been impacted. And most importantly, I have never had to interact with a customer, so even if they were interacting with all of that vibe, they couldn’t associate my face with it. If I had to meet with the clients, I'd prefer to hide my face with a paper bag or something.&lt;/p&gt;
&lt;p&gt;Unfortunately these terrors are often committed by the same AI evangelists that are always telling us to "not trust the AI blindly" in our many agentic coding meetings, with Claude's &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; always enabled. Why have I even had to attend multiple of these meetings anyway? Surely vibe coding hasn’t changed that much in a month that I needed a refresher course.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;The non-coding parts are also not that great in my opinion. We’re churning out so much AI-generated documentation that there’s zero chance that anyone with a beating heart will ever read them. Probably why the text reads like it’s written for other robots. I think the only place where most of the text is written by humans at this point is Slack, and it also has a bunch of AI transcriptions too.&lt;/p&gt;
&lt;p&gt;Jira tickets are also hallucinated by LLMs. You know what’s a right laugh? Getting assigned a third-party integration ticket where all of the details on the external service have been made up by a GPU. Someone just had an AI fill out a whole document out of a single-line "Allow fetching data from [CRM service]". The only usable part was the name of the service. None of the use cases or API endpoints existed. It's fairy dust; it doesn't exist, it's not fucking real.&lt;/p&gt;
&lt;p&gt;I'm not even that opposed to maintaining documentation, but I did try to use Claude to update the &lt;code&gt;CHANGELOG.md&lt;/code&gt; file on my behalf. Unfortunately it managed to generate excruciatingly detailed technical descriptions of all of the changes, so I just deleted all of it, and rewrote it by hand. Hopefully this way the next developer can actually read through the changelog and not ask their AI agent to summarise the changes for them.&lt;/p&gt;
&lt;p&gt;I’ve also noticed that trying to ask Claude about our codebases is also not that great. While debugging a problem that Claude had sidestepped by rigging the tests, I asked Claude to identify if we were validating the data we’re entering into our database. Claude then told me that we don’t have database constraints to validate anything, but we’re checking them in the code before it hits the database. I then looked and there was a function to validate the data, but absolutely nothing called it. And when I unrigged the tests, broken and unvalidated data was entered into the database. Shocking. Thankfully I had already determined the root cause before asking Claude, so I wasn’t led astray and only wasted some time looking up that unused code.&lt;/p&gt;
&lt;p&gt;The single best use case for Claude that I've seen so far is code reviews. Before, I'd first review my changes myself, then I'd push it to remote and ask someone else to review it for me. Now I can first review it myself, then ask Claude what it thinks of my changes, and only then waste another human's time on it. The reason why I like it is that it has good upsides and minor downsides; if it notices something important, great, I can change it, and if it gets an absolutely abhorrent idea for a change, I can just ignore it.&lt;/p&gt;
&lt;p&gt;And since LLMs can understand natural language and code, I can ask one to check if my documentation is technically accurate, which I can then fix myself. As much as I'm shitting on AI, I do actually find this useful. Although not useful enough that I'd actually pay Anthropic for a subscription, and I presume the current prices are heavily subsidized.&lt;/p&gt;
&lt;p&gt;As far as the coding tasks that I entrust Claude, I'm currently keeping it for advanced copy-paste tasks only. By this, I mean that I might write five unit tests for a single model by hand, and then ask it to write tests for another model based on those previous handwritten tests. The way I'd have normally gone about it would've been to copy-paste the original tests anyway, so Claude can do it a bit smarter. Unfortunately, even when I've given it an explicit template for what it needs to write, it still has managed to conjure other test cases from outside my specific example, so even this use case is far from perfect.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;I think code reviews also become quite interesting in terms of development team dynamics. I’ve seen pull requests co-authored by Claude with thousands upon thousands of lines of changes, and I don’t know if I can bring myself to review them. How much of my day am I going to devote to reviewing this change? And will I end up spending more time looking at this code than the guy who supposedly co-authored it with Claude?&lt;/p&gt;
&lt;p&gt;I mean, I’ve definitely had to submit large changesets for review too, but at least I was not asking other people to work harder on reviewing the code than I worked writing it, since those large pull requests took real effort. And I definitely couldn’t open several of them per week.&lt;/p&gt;
&lt;p&gt;So far I’ve been flying under the radar, trying to only snipe the smaller code reviews, and leaving the ones with tens of thousands of lines to other developers. I did start reading one larger change too, but someone approved it before I could really get going. Judging by how fast my coworkers get them approved, I can only assume that they’re checking the changes as rigorously as they are checking &lt;del&gt;their own&lt;/del&gt; Claude’s work.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, I think AI has come to stay when it comes to software development. No idea what kind of a place the industry has for me in the future. The Internet keeps telling me that I am one of the ones who will be replaced. But I just can't imagine getting up in the morning and have the motivation to just prompt a model all day long. Personally, I’ll try to keep writing code by hand as long as I can, just like I write documentation and blog posts by hand, and enjoy the process and struggle.&lt;/p&gt;
&lt;p&gt;I imagine my time at my current place of work is going to be relatively short. My tenure is measured in months, and I'm already pretty sick and tired of the culture. My feelings towards work are so bad that I'd probably sleep better if my employment was terminated tomorrow. Presumably for shipping code too slowly or something, although I don't think anyone has really made a note of how organic my code still is. The only thing that keeps me going is receiving a steady salary, and I've still considered just outright quitting before finding my next job.&lt;/p&gt;
&lt;p&gt;The lesson learned from the current job? Figure out how any prospective employer is using AI before continuing, as nobody brought it up before my hiring, and I only discovered having joined a slophouse when I was already in it, and got told that I am expected to code using AI. Probably would've been better for both parties if they'd just told me ahead of time.&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 31 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/tired-of-ai-coders/</guid></item><item><title>Year in community</title><link>http://notes.eatonphil.com/2025-12-31-year-in-community.html</link><description>&lt;p&gt;This year I ran three &lt;a href="https://eatonphil.com/bookclub.html"&gt;book club&lt;/a&gt;
readings over email with 1,230 unique attendees. I ran 12 &lt;a href="https://eatonphil.com/coffee.html"&gt;coffee
club&lt;/a&gt; meetups in midtown Manhattan
with 170 unique attendees. Angelo and I ran 6 &lt;a href="https://nycsystems.xyz/"&gt;NYC
Systems&lt;/a&gt; meetups with 12 different speakers
and 281 unique attendees. I took 3 visiting PhD students out for &lt;a href="https://eatonphil.com/banhmi.html"&gt;Banh
Mi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I &lt;a href="https://eatonphil.com/chat.html"&gt;raised&lt;/a&gt; $6,915 for educational
non-profits, offering chats in return. I got coffee, lunch, or took 30
minute calls with 55 people I'd never spoken to before in person or on
video. (Most, but not all, were in return for fundraising receipts.)
This list included women and men based in the USA, Germany, Canada,
Nigeria, Nepal, India, the United Kingdom, Brazil, New Zealand, Israel, and
Australia. (I think I'm forgetting one or two.)&lt;/p&gt;
&lt;p&gt;Thank you to every person who has been a part of these efforts, making
them so special and so valuable. See you in the new year!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Year in community &lt;a href="https://t.co/n7jrmsZiKN"&gt;pic.twitter.com/n7jrmsZiKN&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/2006423710459376079?ref_src=twsrc%5Etfw"&gt;December 31, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 31 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-12-31-year-in-community.html</guid></item><item><title>What Ilya Sutskever, Satya Nadella, and Sam Altman think about AI progress in 2025</title><link>https://bytepawn.com/ai-ilya-dwarkesh-satya-altman-2025.html</link><description>&lt;p&gt;This article contrasts how Ilya Sutskever, Satya Nadella, and Sam Altman think about AI progress, from research and generalization, through infrastructure and economics, to agents, scaling, and societal impact.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/sutskever-nadella-altman.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Wed, 31 Dec 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/ai-ilya-dwarkesh-satya-altman-2025.html</guid></item><item><title>The Pro-life Position Leads to Dark Places</title><link>https://blog.rongarret.info/2025/12/the-pro-life-position-leads-to-dark.html</link><description>One of the things I did this year was to engage in one of the most ridiculously long Reddit discussion threads ever, and that is really saying something.&amp;nbsp; That link doesn't even go to the beginning of the thread because the discussion chain was broken by a post that was deleted by my interlocutor, who&amp;nbsp;&amp;nbsp;goes by the pseudonym "uniformist".&amp;nbsp; The discussion branched and ebbed and</description><author>Rondam Ramblings</author><pubDate>Tue, 30 Dec 2025 20:21:12 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/12/the-pro-life-position-leads-to-dark.html</guid></item><item><title>Linux Dev / Productivity Environment on Windows with WSL 2 and Docker</title><link>https://nickjanetakis.com/blog/linux-dev-and-productivity-environment-on-windows-with-wsl-2-and-docker</link><description>We'll balance using Linux through WSL 2 as well as run native Windows apps
to get stuff done related to software development.</description><author>From Development to Production on Nick Janetakis</author><pubDate>Tue, 30 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nickjanetakis.com/blog/linux-dev-and-productivity-environment-on-windows-with-wsl-2-and-docker</guid></item><item><title>If you care about security you might want to move the iPhone Camera app</title><link>http://blog.jgc.org/2025/12/if-you-care-about-security-you-might.html</link><description>&lt;p&gt;There's a quirk in the iPhone Camera app that can drive a security conscious iPhone owner crazy: if you touch your finger on the Camera app icon without actually opening the app, the camera starts operating. That causes the little green dot indicator on the iPhone to turn on and then after a few seconds disappear. This can easily make a security-conscious iPhone user worry that some app is nefariously using the camera.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEh_n7C7-AZVWdKp41tN41JNMVQqj-nzIXSD8K3pHXC9Zijl5Z3wtLA7OXRX0h7QRNDzE6juLUc10qyIoRf_CO5CTF8rtNjuVQ9MkGoDGGrhr-osKlxqOXaNoalhcaVpywdIXsHSCdik2SpothXBzFoLDYKUjKORzLwio-m5KXX7k6tZdacgAyHBzQ" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="154" src="https://blogger.googleusercontent.com/img/a/AVvXsEh_n7C7-AZVWdKp41tN41JNMVQqj-nzIXSD8K3pHXC9Zijl5Z3wtLA7OXRX0h7QRNDzE6juLUc10qyIoRf_CO5CTF8rtNjuVQ9MkGoDGGrhr-osKlxqOXaNoalhcaVpywdIXsHSCdik2SpothXBzFoLDYKUjKORzLwio-m5KXX7k6tZdacgAyHBzQ=w640-h154" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Because touching the Camera app icon without opening the app is enough, it's easy to activate the green dot while swiping between screens or even just holding your phone. This was driving me nuts and I thought some app was using the camera when I didn't want it to.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEgO6PJijthJhA5iuemg6BWxxH9aWJTKJkI-uGMvvzqJlS-75oqgWNswAYCu-IQgT1aAtx99xL68ZS5vnpUe75OBjvYcnd75a1kflRBQvRHqZI1GaxRxMWwFDjZoz23-Qoxw9JSSOetqbS_Pc_vhm3RajJKRArkHQcUq642qVls9Iv9R23z0QWRytQ" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="640" src="https://blogger.googleusercontent.com/img/a/AVvXsEgO6PJijthJhA5iuemg6BWxxH9aWJTKJkI-uGMvvzqJlS-75oqgWNswAYCu-IQgT1aAtx99xL68ZS5vnpUe75OBjvYcnd75a1kflRBQvRHqZI1GaxRxMWwFDjZoz23-Qoxw9JSSOetqbS_Pc_vhm3RajJKRArkHQcUq642qVls9Iv9R23z0QWRytQ=w611-h640" width="611" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I enabled Apple's &lt;i&gt;App Privacy Report&lt;/i&gt; (under P&lt;i&gt;rivacy and Security&lt;/i&gt;) in &lt;i&gt;Settings&lt;/i&gt; and, sure enough, it confirmed that the little green dot was happening because the camera was being used by the Camera app. Merely swiping while accidentally touching the Camera app's icon was enough to add an entry in the &lt;i&gt;App Privacy Report&lt;/i&gt;.&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEj4Fi9G3ADvNdBP5kXYwOl0n4VpXG-rFv_qaxzhd1fVt5v06gpeIUxq3Mm2mJQCGZBEoO-AZY_VaT8SZulmYAlrdCJL-VV4_uxgBUjQf1Jfs3RGdpjBzjtGSefAKaUnGtVLnBc3kRYnW2pcUHGx7B3WwHhjCRzW7unft6WwZMU3ow5MggmgBzGW8Q" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="270" src="https://blogger.googleusercontent.com/img/a/AVvXsEj4Fi9G3ADvNdBP5kXYwOl0n4VpXG-rFv_qaxzhd1fVt5v06gpeIUxq3Mm2mJQCGZBEoO-AZY_VaT8SZulmYAlrdCJL-VV4_uxgBUjQf1Jfs3RGdpjBzjtGSefAKaUnGtVLnBc3kRYnW2pcUHGx7B3WwHhjCRzW7unft6WwZMU3ow5MggmgBzGW8Q=w640-h270" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The solution was moving Camera app away from where I was liable to brush it with my fingers or thumb. That reduced the errant green dots to almost zero and reassured me that nothing untoward was happening.&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEgKcb_6Q4FTr9F23LeQyECWRe7CcdXbyEw7v0JIM6p1suL1OND7t980m81nRDRJQvcqHIcQ3RzVy7MyjjyarsJ-oKjuaDvkV8VfOt65QNuANg9PSkZjvtu8K-8NRdjOXe1AICcpddWIEjJmvmMg66aa3m2DMMNG9LiwzKfIR-z_2OydafFdlUQNHA" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="640" src="https://blogger.googleusercontent.com/img/a/AVvXsEgKcb_6Q4FTr9F23LeQyECWRe7CcdXbyEw7v0JIM6p1suL1OND7t980m81nRDRJQvcqHIcQ3RzVy7MyjjyarsJ-oKjuaDvkV8VfOt65QNuANg9PSkZjvtu8K-8NRdjOXe1AICcpddWIEjJmvmMg66aa3m2DMMNG9LiwzKfIR-z_2OydafFdlUQNHA=w294-h640" width="294" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;One of the big problems in security is people get used to odd behaviour they can't explain and end up overlooking a real problem masked be the things that "always happen". The accidental green dots could have been hiding actual nefarious use of the camera. Better to be tidy and eliminate the false positives.&lt;p&gt;&lt;/p&gt;&lt;div&gt;(I previously used the term "hover" for what I was experiencing and that was causing confusion. This happens if you touch the Camera app icon without actually opening the app.)&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 29 Dec 2025 13:59:06 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/12/if-you-care-about-security-you-might.html</guid></item><item><title>Some flexibility with Go’s sumdb</title><link>https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb</link><description>I noticed this a year or two ago, but forgot to write it up back then.</description><author>ENOSUCHBLOG</author><pubDate>Mon, 29 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb</guid></item><item><title>My Vibe Coded Projects in 2025</title><link>https://blog.nawaz.org/posts/2025/Dec/my-vibe-coded-projects-in-2025/</link><description>&lt;p&gt;This was my first year using &lt;span class="caps"&gt;AI&lt;/span&gt; coding tools. I&amp;#8217;m often asked what I use
&lt;span class="caps"&gt;AI&lt;/span&gt; coding for outside of work. Here I document what I&amp;#8217;ve built with &lt;span class="caps"&gt;AI&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;When I say &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt; coding&amp;#8221;, I&amp;#8217;m excluding asking a chatbot for code and
copy/pasting it into a …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Sun, 28 Dec 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Dec/my-vibe-coded-projects-in-2025/</guid></item><item><title>A Proclamation Regarding the Restoration of the Dash</title><link>https://blog.nawaz.org/posts/2025/Dec/a-proclamation-regarding-the-restoration-of-the-dash/</link><description>&lt;!--  --&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;WHEREAS&lt;/span&gt;&lt;/strong&gt;, the em-dash (—) has long served as the elegant
scaffolding of the English sentence, providing the necessary breadth
for parenthetical thought, sudden turns of phrase, and rhythmic&amp;nbsp;pause;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;WHEREAS&lt;/span&gt;&lt;/strong&gt;, a modern and unfounded prejudice has arisen, wherein the
presence of the em-dash is viewed with suspicion and cited as the …&lt;/p&gt;&lt;/blockquote&gt;</description><author>Beetle Space</author><pubDate>Fri, 26 Dec 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Dec/a-proclamation-regarding-the-restoration-of-the-dash/</guid></item><item><title>doubly dual shuffles</title><link>https://dotat.at/@/2025-12-25-shuffle.html</link><description>&lt;p&gt;Here’s a pearlescent winter holiday gift for you!&lt;/p&gt;
&lt;p&gt;There are four variants of the algorithm for shuffling an array,
arising from two independent choices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whether to swap elements in the higher or lower parts of the array&lt;/li&gt;
&lt;li&gt;whether the boundary between the parts moves upwards or downwards&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The variants are perfectly symmetrical, but they work in two
fundamentally different ways: sampling or permutation.&lt;/p&gt;
&lt;p&gt;The most common variant is &lt;a href="https://dl.acm.org/doi/10.1145/364520.364540"&gt;Richard Durstenfeld’s shuffle
algorithm&lt;/a&gt;, which moves the boundary downwards and swaps
elements in the lower part of the array. Knuth describes it in TAOCP
vol. 2 sect. 3.4.2; TAOCP doesn’t discuss the other variants.&lt;/p&gt;
&lt;p&gt;(Obeying &lt;a href="https://en.wikipedia.org/wiki/Stigler%27s_law_of_eponymy"&gt;Stigler’s law&lt;/a&gt;, it is often called a “Fisher-Yates”
shuffle, but their pre-computer algorithm is arguably different from
the modern algorithm.)&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#the-four-variants" name="the-four-variants"&gt;the four variants&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the pseudocode below, &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; are the inclusive bounds on
the array to be shuffled; the arguments to &lt;code&gt;rand()&lt;/code&gt; are the inclusive
bounds on its return value; and the loop bounds are inclusive too. I
chose this style to make the symmetries more obvious.&lt;/p&gt;
&lt;p&gt;In all variants, it’s possible for the indexes &lt;code&gt;this&lt;/code&gt; (the boundary
between the parts of the array) and &lt;code&gt;that&lt;/code&gt; (chosen at random) to be
the same, in which case the swap is a no-op.&lt;/p&gt;
&lt;p&gt;I could have written the loop bounds as &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; instead of
&lt;code&gt;min+1&lt;/code&gt; and &lt;code&gt;max-1&lt;/code&gt; to make the variants look as similar as possible,
but it’s more realistic to omit the loop iterations when &lt;code&gt;this&lt;/code&gt; and
&lt;code&gt;that&lt;/code&gt; are guaranteed to be equal.&lt;/p&gt;
&lt;p&gt;It should be clear that &lt;code&gt;rand()&lt;/code&gt; is invoked for spans of each size
between 2 and &lt;code&gt;N&lt;/code&gt; (where &lt;code&gt;N = max - min + 1&lt;/code&gt;) so the algorithms
produce &lt;code&gt;N!&lt;/code&gt; possible permutations as expected.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#boundary-moves-down-pick-from-lower" name="boundary-moves-down-pick-from-lower"&gt;boundary moves down, pick from lower&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shuffle(a, min, max)
    for this = max to min+1 step -1
        that = rand(min, this)
        swap a[this] and a[that]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#boundary-moves-up-pick-from-higher" name="boundary-moves-up-pick-from-higher"&gt;boundary moves up, pick from higher&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shuffle(a, min, max)
    for this = min to max-1 step +1
        that = rand(this, max)
        swap a[this] and a[that]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#boundary-moves-down-pick-from-higher" name="boundary-moves-down-pick-from-higher"&gt;boundary moves down, pick from higher&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shuffle(a, min, max)
    for this = max-1 to min step -1
        that = rand(this, max)
        swap a[this] and a[that]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#boundary-moves-up-pick-from-lower" name="boundary-moves-up-pick-from-lower"&gt;boundary moves up, pick from lower&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shuffle(a, min, max)
    for this = min+1 to max step +1
        that = rand(min, this)
        swap a[this] and a[that]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#the-two-kinds-of-shuffle" name="the-two-kinds-of-shuffle"&gt;the two kinds of shuffle&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To merge four variants into two kinds, I’ll introduce some names that
allow me to describe them independently of the direction of iteration.
The &lt;code&gt;todo&lt;/code&gt; part of the array is ahead of the iterator, i.e. lower if
we are moving the boundary downwards, higher if we are moving it
upwards. The &lt;code&gt;done&lt;/code&gt; part of the array is behind the iterator, &lt;em&gt;vice
versa&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Earlier I called &lt;code&gt;this&lt;/code&gt; the boundary between the parts, but that was a
deliberate fencepost error which allowed me to gloss over an important
difference. In fact the boundary is next to &lt;code&gt;this&lt;/code&gt;, and which side
it’s on depends on the kind of shuffle.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#shuffle-by-sampling" name="shuffle-by-sampling"&gt;shuffle by sampling&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first two variants above work by sampling.&lt;/p&gt;
&lt;p&gt;In this kind of shuffle, the indexes &lt;code&gt;this&lt;/code&gt; and &lt;code&gt;that&lt;/code&gt; are on the
&lt;code&gt;todo&lt;/code&gt; side of the boundary. We pick an element at random from the
&lt;code&gt;todo&lt;/code&gt; part of the array, and swap it into position next to the
boundary. Then the boundary is moved so that the picked element is
appended to the &lt;code&gt;done&lt;/code&gt; part of the array.&lt;/p&gt;
&lt;p&gt;The loop invariant or inductive hypothesis is that the &lt;code&gt;done&lt;/code&gt; part of
the array is a random sample of all the elements from the original
array.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#shuffle-by-permutation" name="shuffle-by-permutation"&gt;shuffle by permutation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The last two variants above work by permutation.&lt;/p&gt;
&lt;p&gt;In this kind of shuffle, the indexes &lt;code&gt;this&lt;/code&gt; and &lt;code&gt;that&lt;/code&gt; are on the
&lt;code&gt;done&lt;/code&gt; side of the boundary. First we move the boundary to append an
element from &lt;code&gt;todo&lt;/code&gt; to &lt;code&gt;done&lt;/code&gt;. Then we pick a random index in &lt;code&gt;done&lt;/code&gt;,
and swap the element next to the boundary into that position.&lt;/p&gt;
&lt;p&gt;The loop invariant or inductive hypothesis is that the &lt;code&gt;done&lt;/code&gt; part of
the array is a random permutation of the elements that were originally
in the same prefix or suffix of the array.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-12-25-shuffle.html#coda" name="coda"&gt;coda&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I was inspired to write this by “&lt;a href="https://possiblywrong.wordpress.com/2020/12/10/the-fisher-yates-shuffle-is-backward/"&gt;The Fisher-Yates shuffle is
backward&lt;/a&gt;” and the examples that were added to
&lt;a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle"&gt;Wikipedia’s article on the Fisher-Yates shuffle&lt;/a&gt; a few
months ago.&lt;/p&gt;
&lt;p&gt;The article by “Possibly Wrong” correctly points out that there are
two kinds of shuffle, but it misses out one of the four variants.
Wikipedia’s examples show all four variants but they are cluttered by
conventional array indexing, and it doesn’t explain there are actually
two kinds of shuffle.&lt;/p&gt;
&lt;p&gt;There’s a classic off-by-one mistake that turns a shuffle into
Sattolo’s algorithm for cyclic permutations. It happens when the
random bounds are too narrow, so &lt;code&gt;that&lt;/code&gt; is never equal to &lt;code&gt;this&lt;/code&gt;, and
an element can never land in the same place that it started. This
change has the same effect on both kinds of shuffle.&lt;/p&gt;
&lt;p&gt;I enjoyed revisiting this old classic and trying to beautify it.
I hope you like the results!&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Fri, 26 Dec 2025 01:45:02 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-12-25-shuffle.html</guid></item><item><title>I made another little bedside clock</title><link>https://www.stavros.io/posts/i-made-another-little-bedside-clock/</link><description>&lt;div class="pull-quote"&gt;I call it 'Bedtime'&lt;/div&gt;&lt;p&gt;The other day I saw &lt;a href="https://www.youtube.com/watch?v=S1Q9PZ95SDM"&gt;a video by someone who bought a $16 electronic clock&lt;/a&gt;, and it looked interesting, because that little clock had been taunting me for months by showing up constantly in my AliExpress recommendations.
I held off on buying it because what am I going to do with &lt;a href="/posts/do-not-be-alarmed-clock/"&gt;yet another clock&lt;/a&gt;, but the video said that it has an ESP8266 inside, and that, with a little soldering and programming, you could run &lt;a href="https://esphome.io"&gt;ESPhome&lt;/a&gt; on it!&lt;/p&gt;
&lt;p&gt;Obviously, I didn&amp;#8217;t need any more convincing, though I remembered this clock being listed for $6 for a while, and balked at the $16 the video mentioned.
I ordered &lt;a href="https://www.aliexpress.com/item/1005009145344721.html"&gt;a different listing, which I found for $12&lt;/a&gt;, hoping it would be the same as the one from the video.&lt;/p&gt;
&lt;p&gt;Fairly serendipitously for this purchase, the &lt;a href="/posts/do-not-be-alarmed-clock/"&gt;last bedside clock I made&lt;/a&gt; was showing its age a bit.
Mainly, the dimmest setting on its screen was bright enough to be annoying when I&amp;#8217;m in bed, and the screen has been burned-in quite a bit.
A new monochrome OLED screen is an easy fix, but I&amp;#8217;d prefer a color one (especially if it can be dimmed more), so hopefully I&amp;#8217;d be able to replace the Do Not Be Alarmed clock with this new one.&lt;/p&gt;
&lt;p&gt;The new clock arrived promptly, and I</description><author>Stavros' Stuff</author><pubDate>Thu, 25 Dec 2025 20:04:52 GMT</pubDate><guid isPermaLink="true">https://www.stavros.io/posts/i-made-another-little-bedside-clock/</guid></item><item><title>The Best Things and Stuff of 2025</title><link>https://blog.fogus.me/2025/12/23/the-best-things-and-stuff-of-2025.html</link><description>Whereby I talk about my favorite media/people/ideas that I encountered for the first time in 2025.</description><author>Send More Paramedics</author><pubDate>Tue, 23 Dec 2025 10:17:49 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/2025/12/23/the-best-things-and-stuff-of-2025.html</guid></item><item><title>New 3d  Printer, A new BITX* Release, and it’s almost 2026 already!</title><link>https://miscdotgeek.com/new-3d-printer-a-new-bitx-release-and-its-almost-2026-already/</link><description>&lt;p&gt;Hi. Remember Me? I&amp;#8217;m the guy that hasn&amp;#8217;t blogged in all of 2025. Yeah, that guy. 2025 has been a huge year. I now own my own home. I have a shed to tear down, another shed to move. I fixed up one of my trucks. I bought a newer (2012) car. I got health &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/new-3d-printer-a-new-bitx-release-and-its-almost-2026-already/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/new-3d-printer-a-new-bitx-release-and-its-almost-2026-already/" rel="noopener noreferrer" target="_self"&gt;New 3d  Printer, A new BITX* Release, and it&amp;#8217;s almost 2026 already!&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Tue, 23 Dec 2025 09:05:53 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/new-3d-printer-a-new-bitx-release-and-its-almost-2026-already/</guid></item><item><title>Fix Open Terminal Failed Error with tmux</title><link>https://nickjanetakis.com/blog/fix-open-terminal-failed-error-with-tmux</link><description>It's often caused by version incompatiblities from version upgrades.</description><author>From Development to Production on Nick Janetakis</author><pubDate>Tue, 23 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nickjanetakis.com/blog/fix-open-terminal-failed-error-with-tmux</guid></item><item><title>Year in books</title><link>http://notes.eatonphil.com/2025-12-23-year-in-books.html</link><description>&lt;p&gt;Among the 50 books I read in 2025, I recommend the following 11
non-fiction and 7 fiction works (complete list
&lt;a href="https://www.goodreads.com/user/year_in_books/2025/50930981"&gt;here&lt;/a&gt;). These were
the 18 books that I rated a four or five out of five stars.&lt;/p&gt;
&lt;h3 id="non-fiction"&gt;Non-fiction&lt;/h3&gt;&lt;h4 id="on-writing-well-by-william-zinsser"&gt;On Writing Well by William Zinsser&lt;/h4&gt;&lt;p&gt;This is the third or fourth time I've read this book and it has stood
the test of time. It's been a few years since I last read it so it was
a good reminder that a lot of the things I believe and tell people
about writing actually just came from this book. The last 25% is a bit
of a slog but nonetheless it remains one of the single books I think
every professional should read. 5/5&lt;/p&gt;
&lt;h4 id="i.-asimov-by-isaac-asimov"&gt;I. Asimov by Isaac Asimov&lt;/h4&gt;&lt;p&gt;I really like reading about how writers make their living. I've also
been a modest fan of Asimov's works (I've loved what I've read I just
haven't read that much). I also love to hear stories of
first-generation immigrants to the US and also he lived in New York
his whole life so it was quite enjoyable. 4/5&lt;/p&gt;
&lt;h4 id="hollywood-by-jeanine-basinger-and-sam-wasson"&gt;Hollywood by Jeanine Basinger and Sam Wasson&lt;/h4&gt;&lt;p&gt;This was 100 years of the evolution of the film industry told
basically entirely in disparate interviews edited together. 5/5&lt;/p&gt;
&lt;h4 id="the-chief-by-david-nasaw"&gt;The Chief by David Nasaw&lt;/h4&gt;&lt;p&gt;I love the history and business of newspapers and media. Moreover it's
the guy that Citizen Kane was based on. 4/5&lt;/p&gt;
&lt;h4 id="brazil-by-lilia-moritz-schwarcz-and-heloisa-murgel-starling"&gt;Brazil by Lilia Moritz Schwarcz and Heloisa Murgel Starling&lt;/h4&gt;&lt;p&gt;I have never learned about the history of Brazil and I found this
introduction enjoyable. 4/5&lt;/p&gt;
&lt;h4 id="the-silk-roads-by-peter-frankopan"&gt;The Silk Roads by Peter Frankopan&lt;/h4&gt;&lt;p&gt;I loved the retelling of human history focused on Persia (and later
Iran). 4/5&lt;/p&gt;
&lt;h4 id="louis-d.-brandeis-by-melvin-i.-urofsky"&gt;Louis D. Brandeis by Melvin I. Urofsky&lt;/h4&gt;&lt;p&gt;I have never read about a supreme court justice before. This was a
well-written biography and introduction to the history of law and law
education. 4/5&lt;/p&gt;
&lt;h4 id="personal-history-by-katharine-graham"&gt;Personal History by Katharine Graham&lt;/h4&gt;&lt;p&gt;Once again I love reading about newspapers and media and the business
and history. This was told by the publisher of The Washington
Post. 5/5&lt;/p&gt;
&lt;h4 id="the-snowball-by-alice-schroeder"&gt;The Snowball by Alice Schroeder&lt;/h4&gt;&lt;p&gt;I've had this book about Warren Buffett on my shelf for nearly 10
years and finally went through it this year. A delightful and easy
read despite the bulk. I only am unhappy that it focused more on
family drama than on business decisions. Par for the course with
biographies unfortunately. 4/5&lt;/p&gt;
&lt;h4 id="the-cartiers-by-francesca-cartier-brickell"&gt;The Cartiers by Francesca Cartier Brickell&lt;/h4&gt;&lt;p&gt;This story spanned three or four major wars and a couple of
continents. I didn't think I'd be interested in the history of luxury
businesses but it has a lot in common with certain modern industries
in tech too. You put premiums on relationships and building good faith
and so on. 4/5&lt;/p&gt;
&lt;h4 id="secret-formula-by-frederick-allen"&gt;Secret Formula by Frederick Allen&lt;/h4&gt;&lt;p&gt;A history of Coca-Cola over the last 100 years or so. Lessons on how
they dealt with competition (Pepsi and Keurig Dr. Pepper) and product
revitalization (New Coke, Diet Coke, etc.). Quite an interesting
read. 5/5&lt;/p&gt;
&lt;h3 id="fiction"&gt;Fiction&lt;/h3&gt;&lt;h4 id="the-buffalo-hunter-hunter-by-stephen-graham-jones"&gt;The Buffalo Hunter Hunter by Stephen Graham Jones&lt;/h4&gt;&lt;p&gt;I got into horror fiction last year (not so much slashers but more
just one of the better written categories of genre fiction). This book
was one of my two favorite novels of the year.&lt;/p&gt;
&lt;p&gt;It's a fictional retelling of American history where a Native American
becomes a vampire and takes revenge on American colonizers in the
American West. 5/5&lt;/p&gt;
&lt;h4 id="the-handmaid's-tale-by-margaret-atwood"&gt;The Handmaid's Tale by Margaret Atwood&lt;/h4&gt;&lt;p&gt;This was my other favorite novel of the year. I am embarrassed not to
have read it before. It's a dystopian story about the USA if all women
were required to give birth to deal with a fertility crisis. 5/5&lt;/p&gt;
&lt;h4 id="the-invisible-life-of-addie-larue-by-v.e.-schwab"&gt;The Invisible Life of Addie Larue by V.E. Schwab&lt;/h4&gt;&lt;p&gt;A French woman gets to live forever but everyone she meets forgets her
after leaving her presence. An easy and enjoyable read. 4/5&lt;/p&gt;
&lt;h4 id="vampires-of-el-norte-by-isabel-cañas"&gt;Vampires of El Norte by Isabel Cañas&lt;/h4&gt;&lt;p&gt;I love a good vampire story, and I love fictional retellings using
fantastical horror elements to emphasize atrocities. Vampires employed
by the US military help the US in the 1840s take Texas from
Mexico. 4/5&lt;/p&gt;
&lt;h4 id="the-very-secret-society-of-irregular-witches-by-sangu-mandanna"&gt;The Very Secret Society of Irregular Witches by Sangu Mandanna&lt;/h4&gt;&lt;p&gt;This was a cute cozy mystery about British witches forced to hide from
society, learning how to accept themselves and develop trust in their
community. 4/5&lt;/p&gt;
&lt;h4 id="pride-and-prejudice-by-jane-austen"&gt;Pride and Prejudice by Jane Austen&lt;/h4&gt;&lt;p&gt;I am likewise embarrassed I have not read this before, nor anything
else by Austen. I'm told I haven't rated it highly enough. I will
undoubtedly reread it. I loved the wit. It required closer reading
than I expected. 4/5&lt;/p&gt;
&lt;h4 id="james-by-percival-everett"&gt;James by Percival Everett&lt;/h4&gt;&lt;p&gt;This was a retelling of Adventures of Huckleberry Finn as told by the
slave, Jim. If you saw the movie American Fiction a few years ago,
it's the same author (of the original book). Everett has very
interesting ideas and I look forward to reading more by him. 4/5&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;My 2025 year in books. 18 to recommend among the 50 I read. &lt;a href="https://t.co/mIcbPk7e5x"&gt;pic.twitter.com/mIcbPk7e5x&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/2003630903109775699?ref_src=twsrc%5Etfw"&gt;December 24, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 23 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-12-23-year-in-books.html</guid></item><item><title>Micro Review: Money for Nothing by Thomas Levenson</title><link>https://riffraff.info/2025/12/micro-review-money-for-nothing-by-thomas-levenson/</link><description>&lt;p&gt;I listened to this last summer, but it took me a long time to write something so here it is: the book talks about the first great financial crisis, the South Sea bubble. The actual full title is &amp;#8220;Money for Nothing: The Scientists, Fraudsters, and Corrupt Politicians Who Reinvented Money, Panicked a Nation, and Made &amp;#8230; &lt;a class="more-link" href="https://riffraff.info/2025/12/micro-review-money-for-nothing-by-thomas-levenson/"&gt;Continue reading&lt;span class="screen-reader-text"&gt; "&lt;span class="p-name"&gt;Micro Review: Money for Nothing by Thomas Levenson&lt;/span&gt;"&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://riffraff.info/2025/12/micro-review-money-for-nothing-by-thomas-levenson/"&gt;&amp;lt;span class=&amp;#039;p-name&amp;#039;&amp;gt;Micro Review: Money for Nothing by Thomas Levenson&amp;lt;/span&amp;gt;&lt;/a&gt; appeared first on &lt;a href="https://riffraff.info"&gt;print &amp;quot;Me&amp;quot;&lt;/a&gt;.&lt;/p&gt;</description><author>print "Me"</author><pubDate>Tue, 16 Dec 2025 15:51:19 GMT</pubDate><guid isPermaLink="true">https://riffraff.info/2025/12/micro-review-money-for-nothing-by-thomas-levenson/</guid></item><item><title>Assign an AHK Global Hotkey to Send Text or Copy It to Your Clipboard</title><link>https://nickjanetakis.com/blog/assign-an-ahk-global-hotkey-to-send-text-or-copy-it-to-your-clipboard</link><description>This could be very handy if you find yourself wanting to send a specific
phrase to an application from time to time.</description><author>From Development to Production on Nick Janetakis</author><pubDate>Tue, 16 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nickjanetakis.com/blog/assign-an-ahk-global-hotkey-to-send-text-or-copy-it-to-your-clipboard</guid></item><item><title>What does ChatGPT think about Viktor Orban's 15 years as Hungary's leader?</title><link>https://bytepawn.com/orban-viktor-chatgpt.html</link><description>&lt;p&gt;I asked ChatGPT to summarize the top 10 positives and negatives of Orban Viktor's 15 years as Hungary's leader.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/orban-viktor-chatgpt.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 15 Dec 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/orban-viktor-chatgpt.html</guid></item><item><title>PaxosLease: Extending and Releasing Leases</title><link>https://bytepawn.com/paxoslease-extend-lease.html</link><description>&lt;p&gt;This article extends the basic PaxosLease algorithm with practical lease renewal and explicit release semantics, showing a full working Python/Flask implementation and explaining why the protocol remains safe without disks or synchronized clocks.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/paxoslease-extend-lease.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 15 Dec 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/paxoslease-extend-lease.html</guid></item><item><title>Dependency cooldowns, redux</title><link>https://blog.yossarian.net/2025/12/13/cooldowns-redux</link><description>Three weeks ago I wrote about how we should all be using dependency cooldowns.</description><author>ENOSUCHBLOG</author><pubDate>Sat, 13 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/12/13/cooldowns-redux</guid></item><item><title>Implementing PaxosLease in Python with HTTP Flask</title><link>https://bytepawn.com/paxoslease-python.html</link><description>&lt;p&gt;This article introduces PaxosLease, a simplified Paxos variant that uses expiring leases instead of persistent values to enable fast, reliable master election in distributed systems.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/paxoslease-python.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 13 Dec 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/paxoslease-python.html</guid></item><item><title>I'm moving to Berlin</title><link>https://captnemo.in/blog/2025/12/12/berlin/</link><description>&lt;p&gt;Haven’t posted here in a while, but this is worth an update: &lt;strong&gt;I’m moving to Berlin&lt;/strong&gt;.
I’ve loved living in Bangalore for the last decade (I moved here just after the
first HillHacks in May 2015), but it is time for an adventure.&lt;/p&gt;

&lt;p&gt;For the last couple of years, I’ve spent my time living in
Indiranagar, building communities, helping underline.center,
working on blr.today, organizing events, and really doing things that I care
about. It has been a wonderful time, but we wanted to experience life
elsewhere, and Berlin seems to fit the bill.&lt;/p&gt;

&lt;p&gt;Bangalore’s infrastructure has been in the news, and it is a part of why I’m
moving. An unwalkable footpath on CMH road meant we spent half a year dealing
with a broken elbow this year. At Takshashila, I was taught that I’m not
allowed to bring up civic problems without also coming up with solutions. But
Bangalore is an unsolvable paradox: A city with 50+ unicorns and no walkable
footpaths.&lt;/p&gt;

&lt;p&gt;I’ve fought my personal share of battles against the state, but this is one
where I don’t have any hope of making a difference. I appreciate the work
that the Bangalore Civil Society is doing in attempting to hold the
missing-government accountable - it just isn’t the kind of work that I want
to do. Fighting for basic necessities (clean air, walkable footpaths, open
public spaces, well-funded public transit) shouldn't be anyone's job in a
city as large as Bangalore. I’m &lt;a href="https://en.wikipedia.org/wiki/Exit,_Voice,_and_Loyalty"&gt;picking Exit&lt;/a&gt; (for now).&lt;/p&gt;

&lt;p&gt;Why Berlin? Mainly because I have lots of friends there. Our immigration journey
is still quite early, so maybe I can write about it when things have stabilized
a bit. For now, if you have any Berlin recommendations or connections, please
send them &lt;a href="/contact/"&gt;my way&lt;/a&gt;.&lt;/p&gt;</description><author>Nemo's Home</author><pubDate>Fri, 12 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://captnemo.in/blog/2025/12/12/berlin/</guid></item><item><title>FreeBSD 15.0 post-upgrade reboot loop</title><link>https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/</link><description>&lt;p&gt;This post is less of a deep dive into a bug I ran into upgrading an x86_64 machine from FreeBSD 14.3 to FreeBSD 15 and more of a PSA: I have a possible workaround for anyone that runs into the &amp;#8230; &lt;a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/"&gt;FreeBSD 15.0 post-upgrade reboot loop&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Tue, 09 Dec 2025 21:34:30 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/</guid></item><item><title>Why Is Video Editing So Bad on Linux Compared to Windows with Camtasia?</title><link>https://nickjanetakis.com/blog/why-is-video-editing-so-bad-on-linux-compared-to-windows-with-camtasia</link><description>I recently tried Kdenlive, Shotcut, DaVinci Resolve, Blender and Lightworks
on Linux. They're not even close to Camtasia.</description><author>From Development to Production on Nick Janetakis</author><pubDate>Tue, 09 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nickjanetakis.com/blog/why-is-video-editing-so-bad-on-linux-compared-to-windows-with-camtasia</guid></item><item><title>Manuel was annoyed</title><link>https://annoying.technology/posts/34b471079caf6aa4/</link><description>All three of the files on top have been created/exported/transferred in the exact same way: Exported from the recently discarded (severely underrated, btw) Clips app, then sent (all in one transfer) via AirDrop. How on earth is it possible for them to have different cases on the file extension?!</description><author>Annoying Technology</author><pubDate>Mon, 08 Dec 2025 20:49:11 GMT</pubDate><guid isPermaLink="true">https://annoying.technology/posts/34b471079caf6aa4/</guid></item><item><title>The Bird-Poker Deck</title><link>https://blog.fogus.me/games/checkers-arcade.html</link><description>Whereby I talk about turning a common American Checkers set into an "arcade" for deep games of abstract strategy.</description><author>Send More Paramedics</author><pubDate>Mon, 08 Dec 2025 10:07:49 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/games/checkers-arcade.html</guid></item><item><title>New Search Filtering in Web and API</title><link>https://www.marginalia.nu/log/a_127_index_filtering/</link><description>&lt;p&gt;The search engine recently exposed a fair number of new tools for custom filtering to the API consumers and users of the new UI.&lt;/p&gt;
&lt;p&gt;This was originally going to be an incredibly chaotic update, both annuncing the new features and doing a technical walkthrough of the changes but that ambition turned out a bit &lt;em&gt;too&lt;/em&gt; chaotic, so let&amp;rsquo;s split them up and focus on the feature announcement bit today.&lt;/p&gt;
&lt;h2 id="new-search-filtering-gui"&gt;New Search Filtering GUI&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s now possible to define a custom filter in the GUI, on the &lt;code&gt;marginalia-search.com&lt;/code&gt; version of the website!&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 08 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_127_index_filtering/</guid></item><item><title>Manuel was annoyed</title><link>https://annoying.technology/posts/cb4a19e1a84a40be/</link><description>Ogres Websites are like onions, they have layers!
Thanks for the submission, Private Sender said:
Is this what is meant by the Content Mines? We have to dig through layers of cruft to get to what we’re looking for?</description><author>Annoying Technology</author><pubDate>Sun, 07 Dec 2025 21:31:00 GMT</pubDate><guid isPermaLink="true">https://annoying.technology/posts/cb4a19e1a84a40be/</guid></item><item><title>How To Podcast</title><link>https://www.swyx.io/podcast</link><description>&lt;p&gt;Through my 4 (!) podcasts I obviously have built up a lot of opinions on podcasting over the years. Here's some of them. The two outlier podcasts of our time are Dwarkesh and TBPN, and I will explain my mental model of them in a separate post - basically &lt;a href="https://github.com/swyxio/swyxdotio/issues/410"&gt;the McClusky Curve&lt;/a&gt; applied. the following advice is what I strongly believe for the "average" podcast that wants to do well but not necessarily being full time top tier influencer level. Also, this is primarily for professional/technical interview guest-led podcasts, but I also do have opinions on non-guest-led podcasts as well.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Sun, 07 Dec 2025 07:31:41 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/podcast</guid></item><item><title>Multi-Paxos: Building a Simple Replicated Log in Python</title><link>https://bytepawn.com/multi-paxos-python.html</link><description>&lt;p&gt;This post builds a minimal Multi-Paxos system in Python with Flask HTTP endpoints, showing how repeated Paxos rounds form a fault-tolerant replicated log.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-multi-paxos2.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 07 Dec 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/multi-paxos-python.html</guid></item><item><title>Paxos: Lamport's Crown Jewel for Replicated State Machines</title><link>https://bytepawn.com/lamport-paxos-simple.html</link><description>&lt;p&gt;A straightforward explanation of Paxos using a simple Python Flask HTTP implementation to show how distributed systems achieve consensus in the presence of faults.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-paxos-simple.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 04 Dec 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/lamport-paxos-simple.html</guid></item><item><title>So you want to move from backend code to robotics systems</title><link>https://jodavaho.io/posts/dev-backend-to-robotics.html</link><description>&lt;p&gt;I got a really good question sent to me, and I&amp;rsquo;d like to archive the response here&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I mostly use Go (previously Java) and am exploring Rust for unmanned systems,
particularly embedded work and autonomy like navigation and perception. Given
your domain expertise, I&amp;rsquo;d love to hear your perspective:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;m bullish on Rust long term. If you&amp;rsquo;re coming from Go/Java, then you&amp;rsquo;ve
already got the &amp;ldquo;backend&amp;rdquo; covered, and a &lt;em&gt;lot&lt;/em&gt; of robotics happens in
&amp;ldquo;backend&amp;rdquo;-like code. So, rust here is more of a curiosity / lateral move for
you. Great to try, not required. You could start right now by developing
implementations of robotics-like things and making a backend.&lt;/p&gt;
&lt;p&gt;Very little &amp;ldquo;solving&amp;rdquo; happens on small machines, it happens on on-system
servers running in docker, usually. The small computers do little things,
usually. So, you can definitely get started with pathfinding, optimization,
mapping, image processing in Rust right now, and build small automous systems
that mostly live in something you&amp;rsquo;d call a &amp;ldquo;backend&amp;rdquo;. Then plug a webcam in,
and hit your &amp;ldquo;backend&amp;rdquo; with imagery and you&amp;rsquo;re off to the races.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re more interested in &amp;ldquo;robotics systems&amp;rdquo;, then there&amp;rsquo;s really no
substitute for learning ROS. While ROS is a bit dated, and most serious shops
built their own version, ROS2.0 has a bunch of normal practices in it, and
every custom robotics framework is defacto compared to ROS2.0 nowadays. This
implies C++ and CMake, it&amp;rsquo;s really hard to get around that. Though there are
Rust wrappers and ROS2.0 Rust compliant messaging libraries.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re more interested in &amp;ldquo;on the metal&amp;rdquo; code, then ROS2.0 is still
relevant, but we&amp;rsquo;re more into the firmware level things. C is the king here
(still). Firmware generally does very little more than manage custom hardware
like motors, low level sensors, etc. It often doesn&amp;rsquo;t do the &amp;ldquo;thinking&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;So if you&amp;rsquo;re looking to get into robotics, C and C++ are still the industry
breadwinner.&lt;/p&gt;
&lt;p&gt;Rust is a great language for this domain, but the libraries and industry
adoption is lagging a little. There&amp;rsquo;s just no serious reason to rewrite your
entire stack in Rust at this time, when robotics &lt;em&gt;products&lt;/em&gt; are not very mature
yet. I definitely have seen production Rust code that flies in warzones, so it
does exist, but it&amp;rsquo;s commonly an outlier.&lt;/p&gt;
&lt;p&gt;One huge benefit of Rust that is often overlooked is the immediate
compatability of &lt;em&gt;types&lt;/em&gt;, &lt;em&gt;build systems&lt;/em&gt;, and &lt;em&gt;dependency management&lt;/em&gt; across
the firmware, system, and backend. This is what will eventually cause Rust to
catch up to C++, but it may never replace it and doesn&amp;rsquo;t need to.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Tue, 02 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dev-backend-to-robotics.html</guid></item><item><title>Using sed to Print Specific Lines in a File</title><link>https://nickjanetakis.com/blog/using-sed-to-print-specific-lines-in-a-file</link><description>This could be handy when a stack trace in a large file points to a specific
line, now you can see those lines quickly.</description><author>From Development to Production on Nick Janetakis</author><pubDate>Tue, 02 Dec 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nickjanetakis.com/blog/using-sed-to-print-specific-lines-in-a-file</guid></item><item><title>Langjam Gamejam: Build a programming language and then use it to make a game in 7 days</title><link>https://austinhenley.com/blog/langjamgamejam.html</link><description>&lt;a href="https://austinhenley.com/blog/langjamgamejam.html"&gt;https://austinhenley.com/blog/langjamgamejam.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Fri, 28 Nov 2025 22:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/langjamgamejam.html</guid></item><item><title>The Quiet Earth</title><link>https://www.friendlyskies.net/maybe/the-quiet-earth</link><description>&lt;p&gt;&lt;img alt="The Quiet Earth film still" src="https://www.friendlyskies.net/images/871.png" title="The Quiet Earth film still" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Above: A still image from “The Quiet Earth” (1985)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Lately it&amp;#8217;s been dark here. A dark time of year, a dark political era&amp;#8230;hell, I am afraid I could just go on and on about darkness!&lt;/p&gt;

&lt;p&gt;Everybody&amp;#8217;s got their ways of coping with this, whether that&amp;#8217;s done by the automatic / instinctive side of the personality, or in a more conscious way.&lt;/p&gt;

&lt;p&gt;On the conscious side, one thing that reliably helps me out in dark times is engaging with the personal hobbies and interests that seem to call out to me for attention.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Personal Interests are Kinda Lit&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;This topic by itself has become a huge interest of mine over the years. I like to study the way our personal interests can guide us through life in a mysteriously helpful way. They often do this through symbolic language.&lt;/p&gt;

&lt;p&gt;Our personal interests tend to bring light into the dark corners of our lives, if given proper attention. And even if they don&amp;#8217;t seem directly related to “resolving issues in global politics” or “dealing with that one coworker”, they often do relate to those things, somehow. I&amp;#8217;m often surprised to find just how direct the connection actually is!&lt;/p&gt;

&lt;p&gt;People usually talk about “engagement with personal interests” as something that disconnects us from the broader world, for good or bad&amp;#8230;but my experiences also seem to point out that this is quite the opposite in a lot of ways. &lt;/p&gt;

&lt;p&gt;I believe there is a strong, symbiotic relationship between those two realms: Out there, and in here.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Getting Gripped (by an Interest)&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Related, one of the techniques that&amp;#8217;s seized me lately works a lot like this:&lt;/p&gt;

&lt;ol&gt;
	&lt;li&gt;Get excited about something. Get sucked into it! (Wow, this movie, or wow, this book! Or hobby, or whatever it is&amp;#8230;)&lt;/li&gt;
	&lt;li&gt;Identify that new world and its energy, as a &lt;em&gt;specific&lt;/em&gt; type of view on things, or a specific approach to things. More on this below.&lt;/li&gt;
	&lt;li&gt;Live through that world&amp;#8212;think about your life using the tools and viewpoints from that world.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some of this process comes from my training in Jungian-oriented topics, and some from various corners of personality theory.&lt;/p&gt;

&lt;p&gt;But, this past weekend, one of those excitements snuck up and gripped me something good. And I wanted to share how that worked out, mostly to review, but also in case it can help somebody else.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Roll Tape&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;This time, it was &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/The_Quiet_Earth_(film)"&gt;The Quiet Earth&lt;/a&gt;&lt;/em&gt;, an &amp;#8217;80s movie that grabbed me once again, and sucked me in. It&amp;#8217;s definitely one of my favorite films. &lt;/p&gt;

&lt;p&gt;Independent, plucky, unique, and deep, it&amp;#8217;s also everything that &lt;em&gt;doesn&amp;#8217;t&lt;/em&gt; describe me on the average lazy Sunday.&lt;/p&gt;

&lt;p&gt;In the movie, the main character wakes up to find that he&amp;#8217;s the only person left alive. On the entire planet. This is the result of a scientific experiment in which he took part: Project Flashlight.&lt;/p&gt;

&lt;p&gt;It soon becomes clear that this person bears some responsibility for ending up completely isolated, maybe even for killing almost everyone on the planet. &lt;/p&gt;

&lt;p&gt;There is a tremendous moral burden on this character, but he&amp;#8217;s also just barely realizing that he&amp;#8217;s part of a broader, selfish, shortsighted, killing system&amp;#8230;and now&amp;#8230;&lt;/p&gt;

&lt;p&gt;&amp;#8230;what to do about it?&lt;/p&gt;

&lt;p&gt;&lt;b&gt;This Kind of Topic Helps Me Out Sometimes&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Why this movie? Why this approach?&lt;/p&gt;

&lt;p&gt;Personally, I didn&amp;#8217;t pick this interest. But if I had to guess why my subconscious mind found it interesting: It often really helps me to look at the world from this kind of viewpoint: “Oh no, what&amp;#8217;s happened? Things are so weird and disconcerting lately. And what can I do about it?”&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Solving novel problems&lt;/em&gt; is one way to look at this, and that is one of the skills I have worked hard on in my professional practice in recent years. So, that connection makes sense to me.&lt;/p&gt;

&lt;p&gt;Here are some more specific examples why this helps:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;First&lt;/b&gt;, I notice that I get more energy if I decide that stuff is really weird lately. lol. It&amp;#8217;s more fun to look at the world this way, for me. It opens up the imagination and anyway, my mind has &lt;em&gt;always&lt;/em&gt; been naturally attuned to single out “something weird that&amp;#8217;s going on.”&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Second&lt;/b&gt;: “Maybe I had a part in it, but let&amp;#8217;s find out.” I&amp;#8217;m a character-motivated person. I like to think about my role in things. There&amp;#8217;s some personal-discovery and maybe also a personal redemption arc here, as is usually true for all of us. One of us, is all of us.&lt;/p&gt;

&lt;p&gt;Within each of us is every philosophical perspective, doing some sort of battle against another perspective from time to time. Is it better to be Simple with this project, or a bit more fancy? Is it better to be a pragmatic person, or more sensitive to broader issues? Looking back, or looking forward? Focused on the now, or lost in thought? &lt;/p&gt;

&lt;p&gt;When one of those perspectives wins, another perfectly good perspective tends to lose. And that can be really, really unfortunate, because they&amp;#8217;re also a full palette of helpful tools and techniques.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Third&lt;/b&gt;, I feel kind of alone in dealing with the problems at hand&amp;#8230;but that&amp;#8217;s also a strength. I mean, there&amp;#8217;s always some clear need to link together, if possible, to solve huge problems in society. But there&amp;#8217;s also a separate strength in looking at problems that only seem to affect us. &lt;/p&gt;

&lt;p&gt;(Eventually, maybe we can even publish the problems in a way that helps us link up with others, so whether this “I&amp;#8217;m alone in dealing with this” is a good thing is also an open, process-related question and a matter to sort out over time.)&lt;/p&gt;

&lt;p&gt;&lt;b&gt;And finally&lt;/b&gt;, I get the idea that I can probably make some sense of the problem at hand, but I need to watch closely and make observations. To activate some sensitivity, this powerful tool we can use to analyze and tackle problems.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;How it worked out&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Translating a personal interest, or a fantasy world, into personal perspectives and tools seems to be a really helpful practice so far.&lt;/p&gt;

&lt;p&gt;And I have to say I love the advantages of the scientific angle:&lt;/p&gt;

&lt;ol&gt;
	&lt;li&gt;Patience is rewarded. This is not an action movie. Think through the problem well.&lt;/li&gt;
	&lt;li&gt;Unique perspectives are welcome. Again, &lt;em&gt;we need so many different tools&lt;/em&gt; in life. You never know what can help.&lt;/li&gt;
	&lt;li&gt;We embrace the fact that dark things can happen. Embracing darkness means that we don&amp;#8217;t try to only look on the bright side of everything, because sometimes this is just the same as blinding ourselves. With closed-off perspectives, we close off access to many important problem-solving tools. And darkness is usually just “depth in disguise.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the event, I took up my notebook and drew out a pleasant, organized, scientific grid layout.&lt;/p&gt;

&lt;p&gt;Then at the top of the paper, I wrote: &lt;span class="caps"&gt;PROJECT&lt;/span&gt; &lt;span class="caps"&gt;FLASHLIGHT&lt;/span&gt; &lt;span class="caps"&gt;LOG&lt;/span&gt; (a reference to the movie), merging the movie theme with my own inner world.&lt;/p&gt;

&lt;p&gt;I worked through general descriptions: Something&amp;#8217;s off. What will I try now? What do I need to do?&lt;/p&gt;

&lt;p&gt;This soon included a little to-do list, on the side. “Drink some water.” Never leave out the mundane stuff!&lt;/p&gt;

&lt;p&gt;But also: “Take crucial measurements.”&lt;/p&gt;

&lt;p&gt;For me, this is a thing I do&amp;#8212;I measure my personal energy, how I&amp;#8217;m feeling. It&amp;#8217;s an activity I do while I&amp;#8217;m journaling.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;What Does It Mean?&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;In the end&amp;#8230;I moved ahead in that particular movie-mindset, as the scientist of who-knows-what-is-going-on. &lt;/p&gt;

&lt;p&gt;I decided to do some very specific things next, as experiments, and I did those things, or scheduled them. &lt;/p&gt;

&lt;p&gt;And on a Sunday, no less! Hah.&lt;/p&gt;

&lt;p&gt;I don&amp;#8217;t want to overstate this, but it really just helps to move ahead. For most of us, that&amp;#8217;s most of what we need on any given day! The feeling of forging ahead in life doesn&amp;#8217;t really have to be that fancy.&lt;/p&gt;

&lt;p&gt;And the tools we use to move ahead may also seem fancy, but it&amp;#8217;s important to realize: Even if they just help us keep going, that&amp;#8217;s usually &lt;em&gt;plenty&lt;/em&gt; of help, and who&amp;#8217;s to say if that&amp;#8217;s fancy, if it just works!&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Still, everything&amp;#8217;s dark, and I hate it, ugh&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;These are, in many ways, dark times. For all of us.&lt;/p&gt;

&lt;p&gt;Tracking that, and managing its effect on our lives, can be very hard. Attention to global issues can easily cascade into dark fallout in our personal lives. This is generally true, the less-conscious we are of a need to find our own way of solving important problems in our lives, even the dark ones.&lt;/p&gt;

&lt;p&gt;Circumstances seem dire, and the tools available to us even seem dire sometimes. Even scrolling on a social media site these days feels like flirting with dangerous mental conditions, inviting some new level of anxiety, or depression, or obsession with some disquieting thing in a disconcerting way.&lt;/p&gt;

&lt;p&gt;So, in this case, recognizing and seizing a &lt;b&gt;motivating personal interest, or personal fantasy&lt;/b&gt;, if I can call it that, is a very clear, straightforward win.&lt;/p&gt;

&lt;p&gt;Every global problem is also a personal problem, and movement &lt;em&gt;on either end of that chain&lt;/em&gt; is helpful.&lt;/p&gt;

&lt;p&gt;(And maybe for another time: What the hell has gone wrong with social media, anyway?)&lt;/p&gt;

&lt;p&gt;That&amp;#8217;s it for now everybody! May you also find some weird thing in your life that&amp;#8217;s really interesting and ideally, helpful. :-) &amp;#8212;Marc&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Video: &lt;em&gt;&lt;a href="https://www.youtube.com/watch?v=ONizBiZPLbE" title="The Equaliser (Not Alone) by The Midnight"&gt;The Midnight &amp;#8211; The Equaliser (Not Alone)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description><author>Marc Stuff</author><pubDate>Thu, 27 Nov 2025 02:42:10 GMT</pubDate><guid isPermaLink="true">https://www.friendlyskies.net/maybe/the-quiet-earth</guid></item><item><title>Festive offers for books on Python, Linux, Regular Expressions, Vim and more!</title><link>https://learnbyexample.github.io/programming-deals-2025/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;Here are some awesome deals for programming books and courses during the 2025 festive season.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="my-ebooks"&gt;My ebooks&lt;a class="zola-anchor" href="#my-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Festive offers for my ebooks till 30-November-2025:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;All 13 Books Bundle&lt;/a&gt; — $16 (normal price $36), learn Regular Expressions, Linux CLI tools, Python, Vim and more!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex"&gt;Understanding Python re(gex)?&lt;/a&gt; — FREE (normal price $10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p align="center"&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;&lt;img alt="All books bundle" src="/images/books/all_books_bundle.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="other-deals"&gt;Other deals&lt;a class="zola-anchor" href="#other-deals"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://treyhunner.com/2025/11/python-black-friday-and-cyber-monday-sales-2025/"&gt;Python related deals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://media.pragprog.com/newsletters/2025-11-19.html"&gt;The Pragmatic Bookshelf&lt;/a&gt; — 50% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adamj.eu/tech/2025/11/20/django-black-friday-deals-2025/"&gt;Deals on Django and Git books/software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday"&gt;Huge list of awesome deals&lt;/a&gt; — tools, productivity, books, courses, etc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blackfridaydeals.dev/"&gt;blackfridaydeals.dev&lt;/a&gt; — Hottest Black Friday Deals for Developers&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 26 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/programming-deals-2025/</guid></item><item><title>Is psql's scripting language Turing complete? Or: fibonacci in psql</title><link>http://notes.eatonphil.com/2025-11-26-psqls-scripting-language-turing-complete-or-fibonacci-psql.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/psqls-scripting-language-turing-complete-or-fibonacci-psql"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 26 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-11-26-psqls-scripting-language-turing-complete-or-fibonacci-psql.html</guid></item><item><title>Avoid Interview Prep Courses</title><link>https://agentultra.com/blog/avoid-interview-prep-courses/index.html</link><description>Musings and ramblings on programming and things</description><author>Agentultra</author><pubDate>Mon, 24 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://agentultra.com/blog/avoid-interview-prep-courses/index.html</guid></item><item><title>We Who Endure</title><link>https://andyjohnson.uk/blog/2025/11/23/we-who-endure/</link><description>A short piece provoked by a recent day spent chopping wood. Link</description><author>Digital Apocrypha</author><pubDate>Sun, 23 Nov 2025 17:06:14 GMT</pubDate><guid isPermaLink="true">https://andyjohnson.uk/blog/2025/11/23/we-who-endure/</guid></item><item><title>We should all be using dependency cooldowns</title><link>https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns</link><description>TL;DR: Dependency cooldowns are a free, easy, and incredibly effective way to mitigate the large majority of open source supply chain attacks. More individual projects should apply cooldowns (via tools like Dependabot and Renovate) to their dependencies, and packaging ecosystems should invest in first-class support for cooldowns directly in their package managers.</description><author>ENOSUCHBLOG</author><pubDate>Fri, 21 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns</guid></item><item><title>The Role Bridging Problem</title><link>https://smcleod.net/2025/11/the-role-bridging-problem/</link><description>An observation on functional correctness without domain quality.</description><author>smcleod.net</author><pubDate>Wed, 19 Nov 2025 17:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/11/the-role-bridging-problem/</guid></item><item><title>Digdoku</title><link>https://kevincox.ca/2025/11/18/digdoku/</link><description>&lt;p style="margin: 0; padding: 0;"&gt;I randomly had the idea that it would be interesting to “dig into” all possible Sudoku puzzles. So I created &lt;a class="_1" href="https://digdoku.kevincox.ca/"&gt;Digdoku&lt;/a&gt;. The landing page is an “empty” puzzle, each link leads to a puzzle with one additional square filled. Values that are not possible are excluded. So you can never get an invalid puzzle by clicking links (but you can by editing the URL).&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The concept is simple. Basically for every unset cell it attempts to solve the puzzle with every number from 1-9. If any solution is found that value is possible, otherwise it is omitted.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;This “eager” removal of impossible values means that it functions as a solver as well. By the time you click all of the provided squares (or often sooner) there will be only one possible grid, and it will be revealed.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;However, implementing this simple solution is fairly slow. So some optimizations were necessary. These are primarily around skipping options that are easily determined to be impossible or were already found to be possible. The solver is still mostly a brute-force depth-first solver with some very basic rules put on top (such as eliminating duplicate values in a box/column/row group and if a value can only be in one place as a group it must be that).&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The slowest case I am aware of takes about 100ms on my desktop. It could almost certainly be much faster, I suspect another order of magnitude would be fairly easy to achieve. But in the first version some cases took over six minutes, so I’m calling it good enough for now.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;One downside is that the URL only keeps minimal state, so a lot of options need to be re-eliminated every step of the way. Maybe compiling to WASM and running an incremental solve client-side would be better. Might be something interesting to try. It would also avoid the risk that my single-core VPS gets overloaded if this gets popular. I do have caching enabled, but I suspect after a few clicks hits will become quite rare.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;There is also something about a simple HTML response that I am very fond of. Theoretically you could pre-render every possible grid into a static site. But according to Wikipedia there are 6.6e27 valid Sudoku grids, so I’ll hold off on the pre-render for now.&lt;/p&gt;</description><author>Kevin Cox's Blog</author><pubDate>Wed, 19 Nov 2025 03:45:00 GMT</pubDate><guid isPermaLink="true">https://kevincox.ca/2025/11/18/digdoku/</guid></item><item><title>Manuel was annoyed</title><link>https://annoying.technology/posts/53aec885b10c4ac6/</link><description>Our corporate IT policy enforced the Tahoe upgrade on my work Macbook – now I’m getting these warnings when I connect it to my screen. I do so via a single USB-C connection for both display and power, and I’ve got half a dozen other devices (a webcam, my keyboard, the tiny amp for my mic, a Litra Glow, a Stream Deck…) connected through the integrated USB hub. Everything works as before, so why bother me with this?</description><author>Annoying Technology</author><pubDate>Tue, 18 Nov 2025 21:07:48 GMT</pubDate><guid isPermaLink="true">https://annoying.technology/posts/53aec885b10c4ac6/</guid></item><item><title>2025 Nov Summary</title><link>https://jodavaho.io/now/2025-fnov.html</link><description>&lt;p&gt;Life is really coasting along. There are always bumps, but generally great. I
spend too much time at home, working and such. Of course I&amp;rsquo;m not exercising
enough. But generally, being here lets me see my kiddos the maximal amount, and
also to spend more time on work.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://flightscience.ai"&gt;Flight Science&lt;/a&gt; has been a blast. We&amp;rsquo;re now &lt;a href="https://jodavaho.io/posts/flight-science-recos.html"&gt;fully automated!&lt;/a&gt;.
What a journey it&amp;rsquo;s been.&lt;/p&gt;
&lt;p&gt;My kiddos are four. I can&amp;rsquo;t really justify posting pictures of them online, but
watching them swing in the backyard, fall leaves everywhere, sun setting over
the water. Pure bliss. Of course one was &amp;ldquo;swinging&amp;rdquo; in the traditional sense
(on a swingset), and the other was &amp;ldquo;swinging&amp;rdquo; as in trying to hit them with a
stick, but they both agreed they were swinging and had a good time with the
pun.&lt;/p&gt;
&lt;p&gt;It won&amp;rsquo;t always be this good. But it is pretty good. Just need to get out and
go jogging / lifting, so if anyone reads this and wants to do accountability
buddies or something, you are gonna save my life!&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 17 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/now/2025-fnov.html</guid></item><item><title>Work/Life milestone: automated messaging</title><link>https://jodavaho.io/posts/flight-science-recos.html</link><description>&lt;p&gt;&lt;img alt="FlightScience!" src="https://jodavaho.io/img/flightscience.png" /&gt;&lt;/p&gt;
&lt;p&gt;Big news! As of last week, we&amp;rsquo;re sending fully autonomous flight optimizations directly to aircraft for our partner airlines. Huge personal milestone for me, as I worked exclusively on the optimization and estimation pipelines that do this!&lt;/p&gt;
&lt;h2 id="why-am-i-sending-messages-to-cockpit-screens"&gt;Why am I sending messages to cockpit screens?&lt;/h2&gt;
&lt;p&gt;Flight Science tracks flights for partner airlines, and helps them improve operations. We plug into various and sundry commercial and customer data streams; collate, estimate, and optimize things in real time; generate alerts and recommendations custom built for their objectives; and send them to the cockpit and/or dispatchers to help improve the experience, safety, and efficiency of the flight.&lt;/p&gt;
&lt;h2 id="my-background-here"&gt;My background here&amp;hellip;&lt;/h2&gt;
&lt;p&gt;I joined in 2024, and when it was just me and Andrew, we banged out the initial planning system over a couple of months. It&amp;rsquo;s iterated a dozen times at least, getting faster, more precise, and more reliable over time. I&amp;rsquo;m fond of saying that 8 my 10 favorite systems I&amp;rsquo;ve worked on were here at Flight Science.&lt;/p&gt;
&lt;p&gt;The system has generated and sent messages for almost 1 year, but it involved me sitting at my machine and approving or denying each message. No longer! It&amp;rsquo;s stable and the recommendations are good enough that we don&amp;rsquo;t need to do that anymore.&lt;/p&gt;
&lt;h2 id="what-do-we-send"&gt;What do we send?&lt;/h2&gt;
&lt;p&gt;Notable examples are alerts: &amp;ldquo;Turbulence ahead&amp;rdquo;, Replan: &amp;ldquo;Do this to avoid turbulence&amp;rdquo;, Efficiency: &amp;ldquo;FL320 would save xxx kg of fuel from improved following wind&amp;rdquo;, and Logistical: &amp;ldquo;Flight YYYY is due to land in 13 minutes&amp;rdquo;&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;We have precise models for key parts, optimization engines for estimating some things, and excellent heuristics for others. You can imagine we have to track the weight of all passengers, luggage, fuel, etc, the temperature of every cubic meter of air they fly through, the fractional kilogram of fuel they use for every stage of flight, airline/pilots preferred flight profile, like climb rates and speeds, and so on&amp;hellip; And, we have to track in real time any changes to their plans - so we can look ahead for them.&lt;/p&gt;
&lt;p&gt;Then all this information has to be piped into a real-time planner and quickly provide a batch of alternative actions, which we can score against their preferences and decide to send an alert or plan or whatever. And for each alternative we have to re-calculate &lt;em&gt;projected&lt;/em&gt; values for all of that stuff above.&lt;/p&gt;
&lt;p&gt;Then all this goes to the cockpit through a global satellite/vhf/hf network, and appears on one of those awesome looking little green screens, which they can ignore if they like.&lt;/p&gt;
&lt;p&gt;Like this screen (not our message):&lt;/p&gt;
&lt;p&gt;&lt;img alt="An ACARS terminal" src="https://jodavaho.io/img/acars.png" /&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s all a suggestion for situational awareness. We do measure acceptance rate by tracking the activity of the airplane to see if they adopt the plan, it&amp;rsquo;s much higher than I expected, but more on that later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I&amp;rsquo;m super excited&lt;/strong&gt;.
None of this would be possible if our team wasn&amp;rsquo;t already super plugged into the airline industry. We have excellent early stage partners to develop and test all this.&lt;/p&gt;
&lt;p&gt;Amazing time.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 17 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/flight-science-recos.html</guid></item><item><title>Philipp was annoyed</title><link>https://annoying.technology/posts/581cfc33e2225888/</link><description>If only there was a way for a cloud service to do background tasks without relying on the user’s browser to stay open.</description><author>Annoying Technology</author><pubDate>Sat, 15 Nov 2025 20:33:38 GMT</pubDate><guid isPermaLink="true">https://annoying.technology/posts/581cfc33e2225888/</guid></item><item><title>I converted a rotary phone into a meeting handset</title><link>https://www.stavros.io/posts/i-converted-a-rotary-phone-into-a-meeting-handset/</link><description>&lt;div class="pull-quote"&gt;The meeting stakes are high when you can get hung up on&lt;/div&gt;&lt;p&gt;As you may remember, or completely not know, I have a &lt;a href="/posts/irotary-saga/"&gt;bit of a fascination with old rotary phones&lt;/a&gt;.
Occasionally, when people learn about this fascination, they donate their old rotary phones to me, so I have ended up with a small collection.&lt;/p&gt;
&lt;p&gt;The other thing I have a fascination with is meetings.
Well, I say &amp;#8220;fascination&amp;#8221;, but it&amp;#8217;s more of a burning hatred, really.
One day, a few months ago, I was in one such meeting, as I have been every day since, and I jokingly pretended to get irate about something.&lt;/p&gt;
&lt;p&gt;One of my coworkers laughed and said &amp;#8220;I bet if this were a phone call, you&amp;#8217;d slam the phone down right now&amp;#8221;, and a dread spread over me.
Why &lt;em&gt;didn&amp;#8217;t&lt;/em&gt; I have a phone handset I could slam down?
Had I really become a corporate husk of my former, carefree self, puppeteered by</description><author>Stavros' Stuff</author><pubDate>Tue, 11 Nov 2025 03:48:23 GMT</pubDate><guid isPermaLink="true">https://www.stavros.io/posts/i-converted-a-rotary-phone-into-a-meeting-handset/</guid></item><item><title>The Impossible Triangle of LLM Infra</title><link>https://www.swyx.io/impossible-triangle</link><description>&lt;p&gt;another talk I am giving at Mastra's TypeScript AI conf today https://docs.google.com/presentation/d/1NnQ3H5Bki3vWRRJdVXoCFJ5dsNKH9QrC-eEQ2Z8olck/edit?usp=sharing&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 06 Nov 2025 21:47:03 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/impossible-triangle</guid></item><item><title>Replacing my old desktop with a high-end Linux PC</title><link>https://boyter.org/posts/replacing-my-old-desktop/</link><description>&lt;blockquote&gt;
&lt;p&gt;I am hoping to get another 5 years out of the machine as it is now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My &lt;a href="https://boyter.org/posts/upgrading-my-old-desktop/"&gt;previous post about my desktop&lt;/a&gt; ended with the above. It has been about 5 years and while I was happy with my old desktop I had started looking into agentic workflows using LLM&amp;rsquo;s. After burning $50 in an afternoon just debugging some flows against OpenAI I decided I needed a better solution.&lt;/p&gt;
&lt;p&gt;A little research and I decided to order and build a new desktop that would solve my immediate problems, as well as last another 10 years. My requirements were fairly simple, as much power as possible in as quiet a form as possible.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 03 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/replacing-my-old-desktop/</guid></item><item><title>An individual can change an organization</title><link>http://notes.eatonphil.com/2025-11-03-an-individual-can-change-an-organization.html</link><description>&lt;p&gt;One of the biggest lessons I learned early in my career was from Drew
DeVault at Linode, 10 years ago. He was one of the youngest developers
in the company (only I was younger, at 20, at the time) but he cared
really strongly about thinking through architecture and code decisions
when the culture at the time was, and I love those guys, a little
haphazard.&lt;/p&gt;
&lt;p&gt;Drew had no special position. We all had the same title,
"Developer". But he argued so persuasively and so doggedly even when
the entire organization seemed against him and somehow he eventually
transformed the entire engineering organization.&lt;/p&gt;
&lt;p&gt;That's supposed to be impossible! It was entirely new to me. That you
don't need to wait behind people with more experience to make the
right decision. That you can be part of making the right decision if you
can find the logic and the will to do it.&lt;/p&gt;
&lt;p&gt;It isn't that simple of course. Politics is politics. But there are
plenty of companies with people who will make a good faith effort to
do what makes sense but might, without someone's unasked-for effort,
do not what makes sense but what is popular because what's popular
just kinda seems easiest. And I always like working for these
companies, and for the most part have been able to identify them
during the interview process.&lt;/p&gt;
&lt;p&gt;I learned from Drew to put limited value in seniority. I learned that
it's ok to debate. I learned to be prepared and to try to present the
facts. I learned to be persistent when I wanted change. I learned that
with these skills, it's possible for an individual to redirect the
path of an organization.&lt;/p&gt;
&lt;p&gt;It took a while longer (and me driving one or two people on my team to
quit, to my great regret) to learn &lt;em&gt;when&lt;/em&gt; to do these things and when
to let things go. Still, this lesson from Drew on what's possible
always stands out in my memory. Thank you, Drew.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Drew DeVault taught me a really important lesson early in my career, that it's possible for a person to change the direction of an organization. &lt;a href="https://t.co/EGBRdbIjVO"&gt;pic.twitter.com/EGBRdbIjVO&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1985479378734649759?ref_src=twsrc%5Etfw"&gt;November 3, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 03 Nov 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-11-03-an-individual-can-change-an-organization.html</guid></item><item><title>Pelican on a Bike - Raytracer Edition</title><link>https://blog.nawaz.org/posts/2025/Oct/pelican-on-a-bike-raytracer-edition/</link><description>&lt;p&gt;Simon Willison has a light-hearted benchmark for evaluating LLMs. He
asks them to draw a &lt;a class="reference external" href="https://simonwillison.net/2025/Jun/6/six-months-in-llms/"&gt;Pelican riding on a
bicycle&lt;/a&gt; in
&lt;span class="caps"&gt;SVG&lt;/span&gt;&amp;nbsp;format.&lt;/p&gt;
&lt;p&gt;I decided to do a twist on this. How good are LLMs at drawing a Pelican
on a bicycle using the &lt;a class="reference external" href="https://www.povray.org/"&gt;&lt;span class="caps"&gt;POV&lt;/span&gt;-Ray&lt;/a&gt; ray&amp;nbsp;tracer?&lt;/p&gt;
&lt;p&gt;My prompt …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Sat, 25 Oct 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Oct/pelican-on-a-bike-raytracer-edition/</guid></item><item><title>how to draw a tetrapod</title><link>https://dotat.at/@/2025-10-24-tetrapod.html</link><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Tetrapod_%28structure%29"&gt;Concrete tetrapods&lt;/a&gt; are used to dissipate wave energy in
coastal defences.&lt;/p&gt;
&lt;p&gt;There’s a bit of a craze for making tetrapod-shaped things: recently
I’ve seen people making a &lt;a href="https://program.why2025.org/why2025/talk/QH8SS9/"&gt;plush tetrapod&lt;/a&gt; and a &lt;a href="https://www.printables.com/model/1451067-tetrapod-lamp"&gt;tetrapod
lamp&lt;/a&gt;. So I thought it might be fun to model one.&lt;/p&gt;
&lt;p&gt;I found a nice way to describe tetrapods that relies on very few
arbitrary aesthetic choices.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dotat.at/random/tetrapod.html"&gt;Click here to play with an animated tetrapod&lt;/a&gt; which I made
using &lt;a href="https://threejs.org/manual/"&gt;three.js&lt;/a&gt;. (You can &lt;a href="https://dotat.at/random/tetrapod.js"&gt;see its source code&lt;/a&gt; too.)&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#recipe-for-a-tetrapod"&gt;Click to skip to the recipe&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="a tetrapod constructed inside a cube, with lines marking the tetrahedron enclosed by the cube, and the circles used to construct the tetrapod " src="https://dotat.at/@/2025-10-tetrapod-build123d.png" /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#background-and-reference-material" name="background-and-reference-material"&gt;background and reference material&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is &lt;a href="https://worldwide.espacenet.com/publicationDetails/originalDocument?FT=D&amp;amp;date=19600504&amp;amp;DB=EPODOC&amp;amp;locale=fr_EP&amp;amp;CC=GB&amp;amp;NR=833920A&amp;amp;KC=A&amp;amp;ND=7"&gt;a patent for tetrapods&lt;/a&gt; written by the inventors of
the tetrapod, Pierre Danel and Paul Anglès d’Auriac. It says,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The half angle a at the apex of the cone corresponding to one leg is
between 9° and 14° while the leg length base width ratio b/c for
each leg is between 0.85 and 1.15 and is preferably substantially
1/1.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One manufacturer has a diagram suggesting a taper of 10°.&lt;/p&gt;
&lt;p&gt;A lot of pictures of tetrapods (&lt;a href="https://www.cndolosse.com/tetrapod/Tetrapod.html"&gt;one&lt;/a&gt;, &lt;a href="https://www.fudotetra.co.jp/en/solution/block/tetrapod/"&gt;two&lt;/a&gt;, &lt;a href="https://betonblock.com/en/coastal-protection-and-flood-defences"&gt;three&lt;/a&gt;) have
chunky chamfers at the ends of the legs. In some cases it looked to me
like the angle of the chamfer matches the angle of the edges of the
tetrahedron that the tetrapod is constructed from, which is the same
as the angle of the faces of the cube that enclose the tetrahedron.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#a-surprising-experiment" name="a-surprising-experiment"&gt;a surprising experiment&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So I thought I would try constructing a tetrapod from the corners of a
cube inwards.&lt;/p&gt;
&lt;p&gt;My eyeball guesstimate sketch produced a result that’s amazingly neat.
It’s the main reason I’m writing this blog post!&lt;/p&gt;
&lt;p&gt;Using &lt;a href="https://build123d.readthedocs.io/en/latest/"&gt;build123d&lt;/a&gt; and &lt;a href="https://github.com/jdegenstein/jmwright-CQ-Editor"&gt;CQ-editor&lt;/a&gt;, I started with a cube centred
on the origin enclosing a tetrahedron, with a common vertex at
(100,100,100). I drew a pair of circles near the common vertex, just
touching the faces of the cube and edges of the tetrahedron. These
circles outlined where I thought the edges of the chamfer might be.&lt;/p&gt;
&lt;p&gt;Using a taper angle of 10°, I found another circle centred on the
origin that would be the base of the leg. I drew truncated cones to
make the leg and chamfer, and replicated them to the three other axes
of the tetrahedron.&lt;/p&gt;
&lt;p&gt;With a bit of fettling of the sizes of the first two circles, I had a
very plausible tetrapod!&lt;/p&gt;
&lt;p&gt;To validate this sketch, I found the diameter of the circle around the
leg near the centre of the cube where it just touches the x,y,z axes,
and the distance from that circle to the end of the leg.&lt;/p&gt;
&lt;p&gt;To my surprise, the width and length of the leg both came out very
close to 100!&lt;/p&gt;
&lt;p&gt;It’s good that they are about the same, because that’s what the
inventors of the tetrapod specified. That means my eyeball guesstimate
got the proportions about right. What’s amazing is that they match
half the side length of the cube that I started with!&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#recipe-for-a-tetrapod" name="recipe-for-a-tetrapod"&gt;recipe for a tetrapod&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can &lt;a href="https://dotat.at/random/tetrapod.js"&gt;read the source code for a tetrapod drawn using three.js&lt;/a&gt;
and &lt;a href="https://dotat.at/random/tetrapod.html"&gt;play with the resulting animation&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#preliminaries" name="preliminaries"&gt;preliminaries&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Pick a &lt;strong&gt;size&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The size is the length and diameter of each tetrapod leg, half the
height of the tetrapod as it is constructed balancing on two legs,
and a few percent less than half the height of the tetrapod when
it’s standing on three legs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The tetrapod is constructed inside a cube centred at the origin,
with vertices at (± size, ± size, ± size). Inside the cube is a
tetrahedron, and the tetrapod’s legs point towards its vertices.&lt;/p&gt;
&lt;p&gt;In the picture above, the cube is shaded green, and the
tetrahedron is outlined with straight red lines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We will construct the positive leg along its &lt;strong&gt;main axis&lt;/strong&gt;, the
vector from the origin to the &lt;strong&gt;common vertex&lt;/strong&gt; (+size, +size,
+size).&lt;/p&gt;
&lt;p&gt;The three other legs will be cloned from the positive leg.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The leg is constructed from truncated cones.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;cone&lt;/strong&gt; is described by its &lt;strong&gt;taper&lt;/strong&gt;, &lt;em&gt;r&lt;/em&gt; / &lt;em&gt;a&lt;/em&gt; = tan(θ),
where &lt;em&gt;a&lt;/em&gt; is the distance from the apex of the cone, and &lt;em&gt;r&lt;/em&gt; is
the radius of the circular section of the cone at that distance.&lt;/p&gt;
&lt;p&gt;Every cone’s axis is the main axis, but their apex varies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A cone is truncated by a &lt;strong&gt;circle&lt;/strong&gt; with radius &lt;em&gt;r&lt;/em&gt; and distance
&lt;em&gt;d&lt;/em&gt; from the origin along the main axis.&lt;/p&gt;
&lt;p&gt;(Note that the apex distance &lt;em&gt;a&lt;/em&gt; and origin distance &lt;em&gt;d&lt;/em&gt; differ
when the apex of the cone is not at the origin.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We need two scaffolding cones.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;axis cone&lt;/strong&gt; has its apex at the orign and its sides just
touch the x,y,z axes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;axis_taper = sqrt(2)
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
  derivation
  &lt;p&gt; z = (0,0,1)
  &lt;p&gt; xyz = (1,1,1)
  &lt;p&gt; z · xyz = |z| |xyz| cos(θ)
  &lt;p&gt; 1 = 1 * sqrt(3) * cos(θ)
  &lt;p&gt; a / h = cos(θ) = sqrt(1/3)
  &lt;p&gt; r² = h² − a² = |z|² − a² = 1 − 1/3
  &lt;p&gt; tan(θ) = r / a = sqrt(2/3) / sqrt(1/3) = sqrt(2)
&lt;/details&gt;
&lt;p&gt;The &lt;strong&gt;face cone&lt;/strong&gt; has its apex at the common vertex. Its sides
just touch the faces of the cube along the same lines as the edges
of the tetrahedron.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apex_d = size * sqrt(3)
face_taper = sqrt(1/2)
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
  derivation
  &lt;p&gt; xy = (1,1,0)
  &lt;p&gt; xyz = (1,1,1)
  &lt;p&gt; xy · xyz = |xy| |xyz| cos(θ)
  &lt;p&gt; 2 = sqrt(2) * sqrt(3) * cos(θ)
  &lt;p&gt; a / h = cos(θ) = sqrt(2/3)
  &lt;p&gt; r² = h² − a² = |xy|² − a² = 2 − 2*2/3
  &lt;p&gt; tan(θ) = r / a = sqrt(2/3) / sqrt(4/3) = sqrt(1/2)
&lt;/details&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#circles" name="circles"&gt;circles&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The leg is constructed from four circles: &lt;strong&gt;waist&lt;/strong&gt;, &lt;strong&gt;thigh&lt;/strong&gt;,
&lt;strong&gt;ankle&lt;/strong&gt;, &lt;strong&gt;sole&lt;/strong&gt;. In the picture above, you can see the thigh,
ankle, and sole circles outlined in red. The waist is hidden inside.&lt;/p&gt;
&lt;p&gt;As required by the inventors of the tetrapod, our chosen size is equal
to the diameter of the thigh, and to the distance between the thigh
and the sole.&lt;/p&gt;
&lt;p&gt;The thigh circles just touch each other at the x,y,z axes.&lt;/p&gt;
&lt;p&gt;The foot of the tetrapod, between the ankle and the sole, is the
chamfered part. It is a section of the face cone.&lt;/p&gt;
&lt;p&gt;The ankle circle is chosen so that the taper of the main part of the
leg is about 10°. It turns out that a nice round number, r = size / 3,
makes the taper 10.41°.&lt;/p&gt;
&lt;p&gt;To fill in the core of the tetrapod, between the thigh circles of the
four legs, we will extrapolate the leg taper to find the waist circle
whose centre is the origin.&lt;/p&gt;
&lt;p&gt;Hence,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Locate the thigh circle from its radius in the axis cone&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;thigh_r = size / 2
thigh_d = 0 + thigh_r / axis_taper
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Locate the ankle circle from its radius in the face cone&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ankle_r = size / 3
ankle_d = apex_d - ankle_r / face_taper
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The thigh circle and leg length determine the position of the sole
circle, from which we get its radius&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sole_d = thigh_d + size
sole_r = (apex_d - sole_d) * face_taper
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The thigh and ankle give us the taper of the leg&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;leg_taper = (thigh_r - ankle_r)
          / (ankle_d - thigh_d)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extrapolate back to the origin to find the radius of the waist&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;waist_d = 0
waist_r = thigh_r + thigh_d * leg_taper
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#legs" name="legs"&gt;legs&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Make the positive leg from two truncated cones:&lt;/p&gt;
&lt;p&gt;one between the waist and ankle,&lt;/p&gt;
&lt;p&gt;one between the ankle and sole.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To make another leg, take a copy of the positive leg and
rotate it 180° about the x or y or z axis.&lt;/p&gt;
&lt;p&gt;Do this for all three axes to get all of the legs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The legs overlap where their waists spread out around the origin.
Conveniently, the legs meet at planes, so we can clip at these planes
to make a model where they don’t overlap.&lt;/p&gt;
&lt;p&gt;The positive leg meets the other legs at the three planes through the
origin with normals:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    (1,1,0)
    (1,0,1)
    (0,1,1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#vital-statistics" name="vital-statistics"&gt;vital statistics&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When standing on three legs with one leg vertical, this tetrapod is&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2.09 * size tall&lt;/li&gt;
&lt;li&gt;2.52 * size max wide&lt;/li&gt;
&lt;li&gt;2.25 * size min wide&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The radii of the key circles of the leg are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1/2 * size thigh&lt;/li&gt;
&lt;li&gt;1/3 * size ankle&lt;/li&gt;
&lt;li&gt;0.27 * size sole&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The radii from the vertical axis to the ends of the base legs are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.08 * size to point touching the ground&lt;/li&gt;
&lt;li&gt;1.37 * size to furthest point&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The taper of the leg is 10.4°.&lt;/p&gt;
&lt;p&gt;The underside of the tetrapod is surprisingly close to flat.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0.17 * size between the ground and the cusp between the legs&lt;/li&gt;
&lt;li&gt;9.0° angle between skin of legs and ground&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The middle of each face of the tetrahedron is close to the core of the
tetrapod. That’s partly because of the small 9° angle between the legs
and the ground, which is parallel to a face of the tetrahedron. It’s
also because the thigh circles protrude a tiny amount through each
face, because they are slightly larger than the circles that just
touch the x,y,z axes and the faces of the tetrahedron. These inner
circles have a radius of sqrt(6/25) * size.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The cusp where three legs meet is slightly inside the face, by 0.015 * size.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The thigh circles are larger than the inner circles by 0.010 * size.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-10-24-tetrapod.html#epilogue" name="epilogue"&gt;epilogue&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It almost feels like the shape of the tetrapod was discovered rather
than designed.&lt;/p&gt;
&lt;p&gt;A tetrahedron nested in a cube is a fun framework for a shape. Using
half the cube’s side length to determine the length and width of the
legs seems like a natural choice. Maybe the choice was based on how
close sqrt(6/25) and 1/2 are?&lt;/p&gt;
&lt;p&gt;A third of the side length for the taper is a plausible option that
turns out quite elegant. Maybe the chamfer at the foot is designed? I
like the way it both hints at the cube from which it was constructed,
and presumably has good engineering properties such as making the
corner less prone to damage and easier to remove from its mould.&lt;/p&gt;
&lt;p&gt;One thing I have not yet managed to work out (except by punting the
problem to a CAD kernel) is exactly what ellipse is formed where the
legs intersect. Investigations continue…&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Sat, 25 Oct 2025 00:40:51 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-10-24-tetrapod.html</guid></item><item><title>Transaction pooling for Postgres with pgcat</title><link>http://notes.eatonphil.com/2025-10-24-transaction-pooling-postgres-pgcat.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/transaction-pooling-postgres-pgcat"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Fri, 24 Oct 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-10-24-transaction-pooling-postgres-pgcat.html</guid></item><item><title>That Time I Trashed The Company Mainframe, And The Lesson I Learned</title><link>https://thecodist.com/that-time-i-trashed-the-company-mainframe-and-the-lesson-i-learned/</link><description>&lt;p&gt;I started my career in the fall of 1981, and in the first six months, I wrote a Jovial language source code formatter in Fortran. I had no prior experience in programming jobs or college programming classes; all I knew was what I had taught myself at home.&lt;/p&gt;&lt;p&gt;That formatter&lt;/p&gt;</description><author>The Codist</author><pubDate>Wed, 22 Oct 2025 18:00:44 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/that-time-i-trashed-the-company-mainframe-and-the-lesson-i-learned/</guid></item><item><title>Running Linux on my gaming PC again</title><link>https://benovermyer.com/blog/2025/10/running-linux-on-my-gaming-pc-again/</link><description>&lt;p&gt;With everything that's going on in the world right now, I am increasingly sensitive to the security and privacy implications of the tech I use. While most of my devices are in the Apple ecosystem, there has been one big hole in my device privacy footprint: my gaming PC.&lt;/p&gt;
&lt;p&gt;I have been restricted to the Windows ecosystem on my gaming PC because of the games I regularly play. A few months ago, I reluctantly "upgraded" from Windows 10 to Windows 11. This made me anxious at the time, but then it faded into the background as other things took up my mental bandwidth.&lt;/p&gt;
&lt;p&gt;Recently, though, Linux came back into my awareness. This past month, KDE celebrated its 29th birthday. This news was all over &lt;a href="https://news.ycombinator.com/item?id=45578117" rel="external"&gt;Hacker News&lt;/a&gt;, Mastodon, and some other online social circles I run in. And with Windows 10 formally being sunset, I decided it was probably time to finally switch. Again.&lt;/p&gt;
&lt;p&gt;See, I've tried running a Linux desktop before. Generally it works fine. However, two areas have made me stumble hard in the past: design software and games. I've relied on Affinity Suite for the former, which has no Linux version and no plans to have a Linux version. For the latter, I tend to play games that somehow always have game-breaking bugs on Linux, even with Proton.&lt;/p&gt;
&lt;p&gt;This time around, I've tried a mixture of the latest developments in Linux and a willingness to change what software I use and what kinds of games I play in order to permanently break from Windows.&lt;/p&gt;
&lt;p&gt;So, here's how it's going.&lt;/p&gt;
&lt;p&gt;I chose &lt;a href="https://www.debian.org/" rel="external"&gt;Debian&lt;/a&gt; 13 with &lt;a href="https://kde.org/plasma-desktop/" rel="external"&gt;Plasma&lt;/a&gt; and &lt;a href="https://wayland.freedesktop.org/" rel="external"&gt;Wayland&lt;/a&gt;. Debian is familiar because I've used Ubuntu a lot in the past, but has none of the bloat of the latter distribution. Wayland solves a lot of problems with some games I play, though it does create new problems with some other software. I'm still working through some of those, like Visual Studio Code having bad flickering unless I use a specific set of startup flags.&lt;/p&gt;
&lt;p&gt;Incidentally, here're the options I use to fix the flickering for VSCode on Wayland:&lt;/p&gt;
&lt;pre class="giallo" style="color: #F8F8F2; background-color: #282A36;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #50FA7B;"&gt;code&lt;/span&gt;&lt;span style="color: #BD93F9;"&gt; --use-gl=egl --ignore-gpu-blocklist --enable-gpu-rasterization --ozone-platform=wayland&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choosing new software for old problems has been the bigger jump. Instead of Affinity Photo, I'm trying out &lt;a href="https://www.gimp.org/" rel="external"&gt;GIMP&lt;/a&gt;. GIMP is way better than it used to be. There's still a big learning curve, so it's an ongoing challenge. For vector work, I'm using Inkscape, which does what I need it to do. For office software, I've already been using LibreOffice on Windows for a long time, so that wasn't a hurdle.&lt;/p&gt;
&lt;p&gt;I discovered that Fastmail has a desktop client for Linux. It's pretty new, apparently, but so far it works really well for me.&lt;/p&gt;
&lt;p&gt;For games, well, that's been interesting. A lot of games that didn't work well on X11 now work on Wayland. Some don't. &lt;a href="https://www.reachthefinals.com/" rel="external"&gt;The Finals&lt;/a&gt;, which is my primary shooter these days, currently has a shader bug that makes it unplayable. It was just introduced, so I don't know when it will get fixed. &lt;a href="https://store.steampowered.com/app/2742830/Monster_Train_2/" rel="external"&gt;Monster Train 2&lt;/a&gt;, though, works flawlessly. I'm starting to explore new games that are built for Linux specifically, though, which is where things are getting more interesting. I might write a future post just about that.&lt;/p&gt;
&lt;p&gt;Spotify works just fine on Linux, but I'm starting to make use of and grow my MP3 library again. I just discovered KDE's &lt;a href="https://juk.kde.org/" rel="external"&gt;JuK&lt;/a&gt; for playing music yesterday, and it's surprisingly awesome. That started me down the path of exploring &lt;a href="https://apps.kde.org/" rel="external"&gt;other KDE tools&lt;/a&gt;, and I'm discovering a lot to love.&lt;/p&gt;
&lt;p&gt;I think this is just the beginning of a paradigm shift in how I use my gaming PC. My Mac Mini and Macbook Pro feel a lot less necessary now, too. Time will tell how this shakes out.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Wed, 22 Oct 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/10/running-linux-on-my-gaming-pc-again/</guid></item><item><title>A couple of handy KNX gadgets</title><link>http://blog.jgc.org/2025/10/a-couple-of-handy-knx-gadgets.html</link><description>&lt;p&gt;&lt;a href="https://www.knx.org/knx-en/for-professionals/What-is-KNX/A-brief-introduction/"&gt;KNX&lt;/a&gt; is a European standard used in home automation. It's primarily based around a twisted pair bus transporting DC power (30V) and data at 9600 bits/second. Devices attached to this KNX bus draw power for their operation and receive and send messages called telegrams. The devices are typically things like switches, relays, dimmers, HVAC interfaces, small displays, and blind/shutter controllers.&lt;/p&gt;&lt;p&gt;If you're working with a KNX bus you connect a computer to it via bus interface. These are commonly either a USB interface or an Ethernet/WiFi connection. Either way the computer is able to send and receive telegrams and monitor the bus for debugging. This interface is also used for programming the various KNX devices on the bus (for example, associating pressing a button on a particular switch with a particular light circuit going on or off).&lt;/p&gt;&lt;p&gt;Sometimes it's useful to program devices "off the bus" (i.e. away from the actual installation). For this purpose I put together the simplest of minimal KNX buses: a power supply to inject the necessary 30V, a USB bus interface, and a dangling KNX connector (the red and black thing in this photo) that can be plugged into the device to be programmed.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmKiAPaxQszfqx7GR0jqUEsDoWGLWE50OBkinI3k-xyyf0ot97IiBVwa5eBcZU9BfA6n6mayRuOMG2geYnv48otf3Qf1q2XP5Luz7W5bKLgvEItF_F-ufGlhjdfpbByIH7JJqoKsBiOQ7PzXXrpGIayTFvhuJxAEj0L3N2R88uIT8PcAzHZW6WrA/s1280/knx-1.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="482" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmKiAPaxQszfqx7GR0jqUEsDoWGLWE50OBkinI3k-xyyf0ot97IiBVwa5eBcZU9BfA6n6mayRuOMG2geYnv48otf3Qf1q2XP5Luz7W5bKLgvEItF_F-ufGlhjdfpbByIH7JJqoKsBiOQ7PzXXrpGIayTFvhuJxAEj0L3N2R88uIT8PcAzHZW6WrA/w640-h482/knx-1.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Normally the power supply (on the left) would be wired in permanently, but here I am just using a standard power cord for desktop programming. The USB bus interface is in the middle. There's nothing special about these devices, there are hundreds of KNX manufacturers that interoperate; I just happen to be using an &lt;a href="https://new.abb.com/products/2CDG110144R0011/sv-s-30-160-1-1"&gt;ABB power supply&lt;/a&gt; and &lt;a href="https://new.abb.com/products/2CDG110243R0011/usb-s-1-2"&gt;ABB bus interface&lt;/a&gt;.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;This works great: plug the device to be programmed into the connector on the right and hook the PC running the &lt;a href="https://www.knx.org/knx-en/for-your-home/how-to-start/ets-home-edition/"&gt;KNX programming software ETS&lt;/a&gt; into the bus interface via USB. And apart from the two ABB boxes, all that's needed is a power cord, three &lt;a href="https://www.wago.com/global/installation-terminal-blocks-and-connectors/4-conductor-modular-pcb-connector/p/243-211"&gt;WAGO 243-211 connectors&lt;/a&gt;, and some twisted pair. I used random wires that were lying around, in the real world of KNX installations you'd use proper shielded twisted pair cables!&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The other gadget I built is this custom PCB (possibly the world's simplest PCB):&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_f3LS7Hdf3REwPS7f0G-yNtlumcwnaOBDliY__2k7vv9F4hqltGkuPmeNq9TCko1ClcXnmlIXHNYW2R8Pc5Jkbm4Xlhh2uGIiiJatOFlPcPNYD0idV_Cc4xPzLZAC-gMKGZUVgHa_AX7WXCtMLGpoW9RCqRJwogYJp3okpio4Nqgr8m2D5tqfbA/s1280/knx-2.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_f3LS7Hdf3REwPS7f0G-yNtlumcwnaOBDliY__2k7vv9F4hqltGkuPmeNq9TCko1ClcXnmlIXHNYW2R8Pc5Jkbm4Xlhh2uGIiiJatOFlPcPNYD0idV_Cc4xPzLZAC-gMKGZUVgHa_AX7WXCtMLGpoW9RCqRJwogYJp3okpio4Nqgr8m2D5tqfbA/w640-h424/knx-2.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;div class="separator" style="clear: both;"&gt;It just connects four KNX terminals together in parallel. The pins at the top are the standard WAGO 243-131 pins compatible with the WAGO 243-211 connector. When soldered together it looks like this:&lt;/div&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHpTsUNp-DGoiyukBcSwuGQz0AJWzq9Iffeo9z4R2Tbtvh0iGQNmxAyvww9PyMwldAztD8bwIvv0aVGknE0U7IVElLhT1n2g4tqVvmrhrCSDvZiGId5YtlQs1hGDgCSaSe6RmWWdyZQnjwNVgmm1c6PYA9_dJI3EOFXgF9ARsOY2UWmktIE3aJuw/s1280/knx-3.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHpTsUNp-DGoiyukBcSwuGQz0AJWzq9Iffeo9z4R2Tbtvh0iGQNmxAyvww9PyMwldAztD8bwIvv0aVGknE0U7IVElLhT1n2g4tqVvmrhrCSDvZiGId5YtlQs1hGDgCSaSe6RmWWdyZQnjwNVgmm1c6PYA9_dJI3EOFXgF9ARsOY2UWmktIE3aJuw/w640-h322/knx-3.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;span style="text-align: left;"&gt;In conjunction with a bus interface it lets you tap into an existing KNX bus if there's no bus interface present. Or if it's just more convenient to connect where you are working. You simply unplug some device from the bus, plug the bus into this PCB and then plug the device into the dangling connector.&lt;/span&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The bus will work normally and you'll have access for debugging and programming. (Note: you can do this because the KNX bus can be a tree and so it's acceptable to plug in a spur of a couple of devices to the main bus line).&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSmoitIZiO1hn8Iue1es3bMdYYqtM6_ZDkzG82vqPNHrUTH5lH9EeQfgO7tPWD2Dz2Jpk8fbQDlWrI32CoIg0wqJFcq4oGs0lxigd__lo73m_dXtY_7A1lIVJuaUPBk7fV5Ox3FTOHkVEPI8COe4CnEXB2aFeDynFDGQ5T7ztaFxKcnnRVLoaQfQ/s1280/knx-4.jpeg"&gt;&lt;img border="0" height="464" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSmoitIZiO1hn8Iue1es3bMdYYqtM6_ZDkzG82vqPNHrUTH5lH9EeQfgO7tPWD2Dz2Jpk8fbQDlWrI32CoIg0wqJFcq4oGs0lxigd__lo73m_dXtY_7A1lIVJuaUPBk7fV5Ox3FTOHkVEPI8COe4CnEXB2aFeDynFDGQ5T7ztaFxKcnnRVLoaQfQ/w640-h464/knx-4.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The PCB design is based on Matthias Kleine's &lt;a href="https://github.com/klein0r/pcb-knx-distributor"&gt;KNX Distributor&lt;/a&gt;; my version can be &lt;a href="https://github.com/jgrahamc/knx-tap"&gt;found here&lt;/a&gt;. (If you build this you'll also need Matthias Kleine's &lt;a href="https://github.com/klein0r/pcb-footprint-collection-kicad"&gt;footprint library&lt;/a&gt;).&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 20 Oct 2025 18:33:15 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/10/a-couple-of-handy-knx-gadgets.html</guid></item><item><title>The Bird-Poker Deck</title><link>https://blog.fogus.me/games/bird-poker.html</link><description>Whereby I talk about a truncated pack of playing cards called the Bird-Poker deck...</description><author>Send More Paramedics</author><pubDate>Mon, 20 Oct 2025 11:47:49 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/games/bird-poker.html</guid></item><item><title>The only Permanent Underclass are the ones who believe it is permanent</title><link>https://www.swyx.io/permanent-underclass</link><author>swyx's site RSS Feed</author><pubDate>Mon, 20 Oct 2025 04:02:22 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/permanent-underclass</guid></item><item><title>The design space of AI coding tools</title><link>https://austinhenley.com/blog/aidesignspace.html</link><description>&lt;a href="https://austinhenley.com/blog/aidesignspace.html"&gt;https://austinhenley.com/blog/aidesignspace.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sat, 18 Oct 2025 23:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/aidesignspace.html</guid></item><item><title>Insights a 25 Year Old Movie Can Give Us on LLMs</title><link>https://blog.nawaz.org/posts/2025/Oct/insights-a-25-year-old-movie-can-give-us-on-llms/</link><description>&lt;p&gt;I often hear complaints that &lt;span class="caps"&gt;LLM&lt;/span&gt; coders are not like junior colleagues.
The difference being that junior colleagues learn and grow and don&amp;#8217;t
make the same mistakes over and over. Time spent attending to a
colleague bears long-term&amp;nbsp;fruit.&lt;/p&gt;
&lt;p&gt;LLMs that don&amp;#8217;t learn from their experiences will be …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Wed, 15 Oct 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Oct/insights-a-25-year-old-movie-can-give-us-on-llms/</guid></item><item><title>Fully Implementing PSR-16 Simple Cache is Less Than Simple</title><link>https://donatstudios.com/psr16-iterable-issue</link><description>&lt;p&gt;&lt;strong&gt;Foreword&lt;/strong&gt;: Please note this falls firmly into &amp;quot;nitpick&amp;quot; territory rather than shining light onto any sort of major issue. I just thought it was an interesting gotcha and wanted to share.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: To fully implement the PSR-16 specification as defined, an implementation MUST be contravariant of the defined interface in its accepted parameters when implementing v2 or later.&lt;/p&gt;
&lt;p&gt;I work on an application with a proprietary caching layer. Recently I wanted to build a PSR-16 &amp;quot;Simple Cache&amp;quot; adapter for it, and started going through &lt;a href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md"&gt;the spec&lt;/a&gt; and &lt;a href="https://github.com/php-fig/simple-cache"&gt;provided interface&lt;/a&gt; in detail. I wanted my implementation to be as correct as possible.&lt;/p&gt;
&lt;p&gt;While working my way through &lt;code&gt;getMultiple&lt;/code&gt;/&lt;code&gt;setMultiple&lt;/code&gt;/&lt;code&gt;deleteMultiple&lt;/code&gt; I noted something that seemed like an oversight.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php"&gt;/**
 * Obtains multiple cache items by their unique keys.
 *
 * @param iterable&amp;lt;string&amp;gt; $keys    A list of keys that can be obtained in a single operation.
 * @param mixed            $default Default value to return for keys that do not exist.
 *
 * @return iterable&amp;lt;string, mixed&amp;gt; A list of key =&amp;gt; value pairs. Cache keys that do not exist or are stale will have $default as value.
 *
 * @throws \Psr\SimpleCache\InvalidArgumentException
 *   MUST be thrown if $keys is neither an array nor a Traversable,
 *   or if any of the $keys are not a legal value.
 */
public function getMultiple(iterable $keys, mixed $default = null): iterable;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take a moment to read this carefully&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@throws \Psr\SimpleCache\InvalidArgumentException
  &lt;strong&gt;MUST be thrown if $keys is neither an array nor a Traversable&lt;/strong&gt;
  or if any of the $keys are not a legal value.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, inspect the definition&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php"&gt;public function getMultiple(&lt;strong&gt;iterable $keys&lt;/strong&gt;, mixed $default = null): iterable;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The takeaway here should be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;iterable $keys&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To implement &lt;code&gt;getMultiple&lt;/code&gt;, we &lt;strong&gt;MUST&lt;/strong&gt; throw a &lt;code&gt;\Psr\SimpleCache\InvalidArgumentException&lt;/code&gt; when &lt;code&gt;$keys&lt;/code&gt; is invalid, yet &lt;code&gt;$keys&lt;/code&gt; is defined as &lt;code&gt;iterable&lt;/code&gt; which makes it impossible to handle &lt;em&gt;if an implementation matches this signature&lt;/em&gt;. &lt;/p&gt;
&lt;p&gt;Passing anything non-iterable will throw a &lt;code&gt;TypeError&lt;/code&gt; before the code even runs. This would make it impossible to throw an InvalidArgumentException.&lt;/p&gt;
&lt;p&gt;Parameter types were added to the interface with the release of &lt;code&gt;v2&lt;/code&gt;. My suspicion was that the &lt;code&gt;@throws&lt;/code&gt; had simply not been updated, because if the parameter type is explicitly defined as iterable, the function cannot throw a custom exception.&lt;/p&gt;
&lt;p&gt;I originally suspected this would make it impossible to fulfill the spec. However, I had not considered contravariance. The implementing methods accepted parameter types may be wider than the interface it is fulfilling. In this instance, &lt;code&gt;$keys&lt;/code&gt; does not need to be defined as &lt;code&gt;iterable&lt;/code&gt; in the implementation.&lt;/p&gt;
&lt;p&gt;This means a technically complete implementation must omit &lt;code&gt;iterable&lt;/code&gt; and look something like&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php"&gt;public function getMultiple($keys, mixed $default = null): iterable {
    if(!is_array($keys) &amp;amp;&amp;amp; !$var instanceof Traversable) {
        // Must implement \Psr\SimpleCache\InvalidArgumentException;
        throw new ExampleException;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;small&gt;Note &lt;code&gt;\Psr\SimpleCache\InvalidArgumentException&lt;/code&gt; is an interface. An implementation must define a concrete exception class that implements it before it can be thrown&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;So while it's not &lt;em&gt;impossible&lt;/em&gt; to implement, it certainly is strange to implement. One can only correctly implement the spec by manually checking &lt;code&gt;$keys&lt;/code&gt; type like this.&lt;/p&gt;
&lt;p&gt;Doing a little investigation, it seems like most major implementations still target &lt;code&gt;v1 || v2 || v3&lt;/code&gt; and as &lt;code&gt;v1&lt;/code&gt; did not have typed parameters, neither do the implementations iterable parameters. This means most major implementations are implemented correctly.&lt;/p&gt;
&lt;p&gt;However, there are at least a couple of implementations that get this wrong.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/laminas/laminas-cache/blob/969a664de32938c08a2c7a0181f47c6eef38155c/src/Psr/SimpleCache/SimpleCacheDecorator.php#L172"&gt;laminas/laminas-cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yiisoft/cache/blob/20d3671d9f419116400a885f826180d66573941c/src/ArrayCache.php#L77"&gt;yiisoft/cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PHPSocialNetwork/phpfastcache/blob/974f404318fab53dfc41cc48aa0eca47c046fcba/lib/Phpfastcache/Helper/Psr16Adapter.php#L142"&gt;phpfastcache/phpfastcache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm sure there are others, but I found these three in my quick search. It is a very easy mistake to make when not implementing &lt;code&gt;v1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is not Java. There are no checked exceptions forcing anyone's hand. In practice, this is a minor quirk of the spec. If a &lt;code&gt;TypeError&lt;/code&gt; gets thrown instead of an &lt;code&gt;\Psr\SimpleCache\InvalidArgumentException&lt;/code&gt;, almost certainly nothing meaningful breaks. I doubt anything depends on this behavior. It’s just an inconsistency between what the spec says and what implementations are doing. It is nothing to lose sleep over, but it might be worth fixing for consistency's sake.&lt;/p&gt;
&lt;p&gt;In summary, to fully implement the &lt;code&gt;getMultiple&lt;/code&gt;/&lt;code&gt;setMultiple&lt;/code&gt;/&lt;code&gt;deleteMultiple&lt;/code&gt; methods of the PSR-16 spec, one cannot simply copy their definitions directly from the CacheInterface. The implementation must check the validity of their iterable parameters and not have them defined as an &lt;code&gt;iterable&lt;/code&gt;.&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Mon, 13 Oct 2025 12:42:59 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/psr16-iterable-issue</guid></item><item><title>My startup diary: Techstars</title><link>https://austinhenley.com/blog/techstarsdiary.html</link><description>&lt;a href="https://austinhenley.com/blog/techstarsdiary.html"&gt;https://austinhenley.com/blog/techstarsdiary.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Mon, 13 Oct 2025 02:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/techstarsdiary.html</guid></item><item><title>L.A. Noire review</title><link>https://burakku.com/blog/la-noire-review/</link><description>&lt;p&gt;&lt;img alt="L.A. Noire" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Cinematic brilliance on the Sunset Boulevard.&lt;/p&gt;
&lt;p&gt;I absolutely adore the writing and the story in this game. It’s so masterfully done, both gritty and hilarious, and really pulls you into the story and the setting. This also applies to the characters, who all have excellent characterisation and dialogue, including your man Cole Phelps himself. Anyone who is a fan of detective novels, noir, or just great dialogue should definitely pick up L.A. Noire. I think the biggest negative thing I can say about any of it is that you can only experience it for the first time once.&lt;/p&gt;
&lt;p&gt;The gameplay’s good too. Given the fact that this is an open-world game from Rockstar Games, one might assume that it’s basically a police version of Grand Theft Auto, but it really isn’t. You go around finding clues, interviewing witnesses and suspects, doing police legwork. Sure, some mechanics like tailing cars or engaging in firefights is similar to many other games, but it’s mostly a very unique, and detective-like gameplay experience. And really, even if it was just a lazier Grand Theft Auto clone, the story would still be enough to carry it.&lt;/p&gt;
&lt;p&gt;I think the biggest criticism I’d have for the gameplay is that some of the interrogations require some pretty out-of-the-box thinking and leaps of logic. If someone completes this game with all of the right answers without looking them up, I’d have some concerns about their thought processes. Thankfully most of the interrogation questions are sane, and even the ones where Cole Phelps goes off on a limb do at least have some perverted logic behind it.&lt;/p&gt;
&lt;p&gt;The facial capture technology in L.A. Noire is also quite fascinating. It really gives the characters a lot of emotion and range, a lot more than you’d expect out of a game from 2011. Quite impressively I was able to recognise some of the actors from their faces alone, so that should say something. Unfortunately the texture quality in the game isn’t the highest, so it does water down the magic of the technology a bit. I also don’t think Team Bondi really had to go so hard when it came to creating the facial capture technology, since a lot of the facial clues that you need to read aren’t that subtle. Thankfully too, since a lot of us are not trained detectives. However, hard they went and we got this very unique gaming experience that borders on a TV show as a result.&lt;/p&gt;
&lt;p&gt;Apart from the super-unique facial scanning technology, graphics in the game are quite average. It’s not ugly, but shows its age. Unfortunately one of the bigger issues I have with the game is on the graphics side: the game is capped at a hard 30 frames per second. It also makes the game feel so sluggish that I took a long break in middle of my playthrough because it was so demotivating in an otherwise interesting game. Thankfully now you can play the game with the excellent &lt;a href="https://github.com/VaanaCZ/LANVP"&gt;V-Patch&lt;/a&gt; and enjoy a fairly problem-free 60 FPS, or even &amp;gt;100 FPS if you don’t mind having to turn it down for at least one of the cases. Personally, I stuck to 60 frames per second, as it seems to have no issues apart from more twitchy driving controls, and it fixed the game feeling so sluggish to play.&lt;/p&gt;
&lt;p&gt;But even if the graphics aren’t that spectacular, you can really tell that a lot of effort was spent on the game. While I can’t speak for what Los Angeles was in 1947 (or even what it’s like even now), it does really feel like driving through a meticulous period piece. Team Bondi also really took the extra mile when it comes to the cars too. Despite featuring a lot of driving in an open world, L.A. Noire isn’t really a proper sandbox or driving game, and yet they saw it necessary to fit almost a hundred real-life cars from the period. I doubt that the illusion would’ve been broken if they had half the amount, especially given how often you’re using your police-issued vehicle, but they went hard here too.&lt;/p&gt;
&lt;p&gt;The music and the sounds of the game are top notch too, and like the setting and cars, builds your immersion for a 1940s Los Angeles. I just had to go and listen the soundtrack after I ended my 48-hour completionist playthrough. And since the game is full of acting talent giving their motion and facial performances, the voice acting is also pretty much as good as it gets. Without going into spoilers, there is a particular scene where Aaron Staton gives off an emotional performance as Cole Phelps that I still think about.&lt;/p&gt;
&lt;p&gt;L.A. Noire is such a special game and unfortunately we might not see another game quite like it ever again. The development of the game was tumultuous, Team Bondi folded after the development ended, and I have no idea if the motion capture technology they spent all of that time and money creating even exists anymore. And despite all of that, this wonderful, one-of-a-kind game got made. I would highly recommend everyone pick it up and experience it for themselves.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sun, 12 Oct 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/la-noire-review/</guid></item><item><title>Marketing Effectiveness and Experimentation Measurement</title><link>https://bytepawn.com/marketing-experimentation-measurement.html</link><description>&lt;p&gt;A deep dive into how to structure a marketing measurement and experimentation framework — from universal control groups and campaign holdouts to attribution windows, outlier handling, and statistical rigor. Based on lessons from building a real-world policy for large-scale marketing analytics.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/marketing-experimentation-measurement.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 12 Oct 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/marketing-experimentation-measurement.html</guid></item><item><title>New Art Tool: Kashi's Pixel Playground</title><link>https://www.friendlyskies.net/notebook/new-art-tool-kashi-s-pixel-playground</link><description>&lt;p&gt;I&amp;#8217;m happy to share a new art-creation project, &lt;b&gt;Kashi&amp;#8217;s Pixel Playground&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the direct link to have a play: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.friendlyskies.net/other-projects/kpp/"&gt;https://www.friendlyskies.net/other-projects/kpp/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;This project was inspired by some colored pencils I like to use, which have a bunch of different colors inside each pencil.&lt;/li&gt;
	&lt;li&gt;Desktops / laptops work best for now. Mobile support is on the to-do list.&lt;/li&gt;
	&lt;li&gt;This project was created using SpiderBasic, as a fun retro-coding-style project.&lt;/li&gt;
	&lt;li&gt;Detailed writeup w/ code: &lt;a href="https://forums.spiderbasic.com/viewtopic.php?t=2850"&gt;https://forums.spiderbasic.com/viewtopic.php?t=2850&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Marc Stuff</author><pubDate>Sat, 11 Oct 2025 06:46:27 GMT</pubDate><guid isPermaLink="true">https://www.friendlyskies.net/notebook/new-art-tool-kashi-s-pixel-playground</guid></item><item><title>Modern Monetary Theory: What’s Missing from the Narrative</title><link>https://bytepawn.com/modern-monetary-theory-missing-from-narrative.html</link><description>&lt;p&gt;I explore several points that are rarely explained in Modern Monetary Theory narratives: from taxation in non-tax economies to central bank independence, inflation targets, and the many forms of money that exist beyond government spending. These are areas where the story of MMT could be told more completely.&lt;br /&gt;&lt;br /&gt;&lt;img alt="" src="/images/mmt-island-money.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 11 Oct 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-monetary-theory-missing-from-narrative.html</guid></item><item><title>Status II</title><link>https://andyjohnson.uk/blog/2025/10/10/status-ii/</link><description/><author>Digital Apocrypha</author><pubDate>Fri, 10 Oct 2025 15:34:37 GMT</pubDate><guid isPermaLink="true">https://andyjohnson.uk/blog/2025/10/10/status-ii/</guid></item><item><title>The MMT Case for Governments Funding Open Source Software</title><link>https://bytepawn.com/modern-monetary-theory-open-source.html</link><description>&lt;p&gt;I argue that widely used digital infrastructure like operating systems, compilers and browsers deserve public investment, just like roads, hospitals, and schools.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/mmt-opensource.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Tue, 07 Oct 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-monetary-theory-open-source.html</guid></item><item><title>Status I</title><link>https://andyjohnson.uk/blog/2025/10/06/status/</link><description/><author>Digital Apocrypha</author><pubDate>Mon, 06 Oct 2025 22:33:00 GMT</pubDate><guid isPermaLink="true">https://andyjohnson.uk/blog/2025/10/06/status/</guid></item><item><title>Verizon's I'm A Teapot Error And Other Technology Fails</title><link>https://thecodist.com/verizons-im-a-teapot-error-and-other-technology-fails/</link><description>&lt;p&gt;This is not my week for using technology. None of these problems was my fault, other than my existing in the universe and the attraction that brought.&lt;/p&gt;&lt;p&gt;Today, I wanted to review my Verizon mobile account, and I noticed that the website had changed since my last visit. I clicked&lt;/p&gt;</description><author>The Codist</author><pubDate>Mon, 06 Oct 2025 22:20:32 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/verizons-im-a-teapot-error-and-other-technology-fails/</guid></item><item><title>Language Support for Marginalia Search</title><link>https://www.marginalia.nu/log/a_126_multilingual/</link><description>&lt;p&gt;One of the big ambitions for the search engine this year has been to enable searching in more languages than English, and a pilot project for this has just been completed, allowing experimental support for German, French and Swedish.&lt;/p&gt;
&lt;p&gt;These changes are now live for testing, but with an extremely small corpus of documents.&lt;/p&gt;
&lt;p&gt;As the search engine has been up to this point built with English in mind, some anglo-centric assumptions made it into its code. A lot of the research on search engines generally seems to embed similar assumptions.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 06 Oct 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_126_multilingual/</guid></item><item><title>Control Ultimate Edition review</title><link>https://burakku.com/blog/control-ultimate-edition-review/</link><description>&lt;p&gt;&lt;img alt="Control" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Rather be out of control.&lt;/p&gt;
&lt;p&gt;The story of Control is weird. You go into an empty government building to look for your little brother, meet a strange Finnish man, and get appointed as the director of the bureau within minutes. And unfortunately, it never gets any better. One of the characters you meet at the beginning of the game succinctly describes the story: "This is fucking unbelievable."&lt;/p&gt;
&lt;p&gt;&lt;img alt="This is fucking unbelievable." src="unbelievable.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I feel like the story is trying way too hard to be weird and mysterious, to a point where I just completely lost interest. There's just a limit to weird cutscenes with strange double talk happening that I can process before I just zone off, and after that, there’s no amount of extreme close-ups of Jesse’s eyes that can make me start to care about her. I'd love a nice mystery story, but this isn't a mystery. It's just bizarre for the sake of being bizarre with zero intrigue except for "when does this start making sense?" But it really never does and has very little narrative closure.&lt;/p&gt;
&lt;p&gt;Really, the only good thing going for the story is that there isn’t much of it. It took me eight and a half hours to beat the game from start to finish, and I wasn’t even playing particularly efficiently. Hell, considering I stopped playing the game for two years out of sheer disinterest, I even had time to forget how to play. But despite its short duration, it still feels padded out. The chapter Finnish Tango doesn’t seem to really have any plot significance, and just highlights how Remedy is a Finnish company and gives Martti Suosalo some work.&lt;/p&gt;
&lt;p&gt;The combat starts off super disappointing when you're given the pistol. It's a total peashooter and feels super unsatisfying to use. Enemies will take at least three shots to the head with it before they even consider dying. I actually found it easier to rapid-fire enemies to the body than bother wasting your time aiming for the head. Your player character is also squishy, so being quick on the trigger was less likely to result in your untimely death. I'm surprised that a character in the story managed to commit suicide using this gun considering how little damage a shot to the head from it will do.&lt;/p&gt;
&lt;p&gt;Thankfully the combat does actually open up considerably like 30 minutes afterwards when you're given your first magic power. I'm surprised that the developers decided to not give it to the players immediately considering how bad the firearm is. Or at the very least, do very minimal combat with the firearm before you're given the ability to magically hurl objects very fast at enemies. That being said, even with the magical powers, the combat is still not really amazing. It just stops being awful and starts being playable.&lt;/p&gt;
&lt;p&gt;There’s a new collectible to grab like every 10 metres, but like the story and the characters, I’m struggling to find a reason to care about any of them. There’s also some form of crafting and side missions, and I’m not particularly interested in a game’s side hustles if the main parts are uninteresting. Apparently there’s also more magic powers than just throwing and levitation, but since those are not on the critical path, they’re surely not that important either.&lt;/p&gt;
&lt;p&gt;I would otherwise consider the graphics to be a rare good thing about Control, but unfortunately the graphics are extremely buggy so I can’t even say that. Some enemies have the magic power of spawning pitch-black rectangles all over the screen, which makes the game very hard to play. I’m playing on an RTX 4090, so at least the issue isn’t a lack of horsepower. Thankfully I wasn’t wasteful enough to get the RTX 5090 when it came out, since apparently the game is even buggier on the 50-series cards than it is on my 40-series.&lt;/p&gt;
&lt;p&gt;Not that the game needed to even really shine that much graphically given how bland the game is stylistically. Granted, it’s set inside a government building so blandness is on brand, but it doesn’t make it any more appealing to the eyes. You will probably not lose any enjoyment even if you can’t see the office  fluorescent tube not shine off the marble floor.&lt;/p&gt;
&lt;p&gt;At least the music’s good. I’d definitely listen to Take Control again. But that’s about the only thing Control that I want to take with me. At least I don’t have to feel miffed about not having access to Alan Wake II on Steam.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 06 Oct 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/control-ultimate-edition-review/</guid></item><item><title>Organizational Lock-in</title><link>/organizations/2025/10/organizational-lockin.html</link><description>&lt;p&gt;I worked in an organization with lots of processes. I was in an office with no windows, a bit of a hole or maybe even oubliette. You couldn’t tell time of day, weather, etc but it was better than a cubicle.&lt;/p&gt;

&lt;p&gt;My door opened to a 10 foot hallway with and about 5 feet down there was a large window. It was a weird L-shape and no one else’s office or cube or anything was in the hallway to nowhere. So it was kind of nice that no one walked by and it was off the beaten path.&lt;/p&gt;

&lt;p&gt;The window in the hallway was closed with blinds. But if I opened them it was possible to get some ambient light effects so while I couldn’t see out it from inside my office, I could tell if it was day or night. So it was better.&lt;/p&gt;

&lt;p&gt;A few weeks went by and the blinds were closed one day. That’s weird, no one comes by here. I asked some nearby coworkers if they knew who closed and why and they didn’t know. No one knew. The entire floor is probably the size of a football field and has 100 offices around the edge with 200 cubes and conference rooms and stuff in the middle. My team is probably 10 people and there’s lots of other groups.&lt;/p&gt;

&lt;p&gt;No one I knew knew and didn’t really care. I maybe spent 2-3 minutes because I was surprised that anyone walked by here, much less cared. I opened the blinds and when about my business.&lt;/p&gt;

&lt;p&gt;About a week later, the blinds were closed again. Weird, but again I checked and no one I knew cared. I opened them again.&lt;/p&gt;

&lt;p&gt;3 days later, they were closed. Repeat a few times and now I was opening them every day and someone else was closing them. Weird and funny.&lt;/p&gt;

&lt;p&gt;This went on for a few days and the blinds would close in the middle of the day. I spent about 3-6 hours out of my office doing different stuff so I come and go. So I would now open them whenever I noticed them back down during the day.&lt;/p&gt;

&lt;p&gt;Finally, I’m in my office and I hear the metallic zip sound of blinds closing. I stick my head of of my office and say hello to someone from a different floor and group, I’ll call her Betty.&lt;/p&gt;

&lt;p&gt;I ask Betty what’s up and she tells me that she’s closing the blinds. I ask why and she says because they are supposed to be closed. I explain why I like them open and that there’s no one else who sees this window and she again says the blinds must be down.&lt;/p&gt;

&lt;p&gt;Interesting. I love arbitrary rules as a big board game fan. I ask about why, and she says that no building things can be changed without a business justification as required by OSHA, fire marshalls, and emergency coordinators.&lt;/p&gt;

&lt;p&gt;I tell her that I think this wouldn’t apply to something as minor as a window blind, but she assured me it’s for everything.&lt;/p&gt;

&lt;p&gt;I try a different tack and propose that the natural state is open, and that she is actually the one who needs a justification to close. But she’s too smart for that, she produces an inventory of the building that she makes each January that clearly shows the blinds closed for the past few years.&lt;/p&gt;

&lt;p&gt;She is not joking or being ironic. She’s serious.&lt;/p&gt;

&lt;p&gt;So I ask how business justifications are evaluated and she says that they are submitted to her and she evaluates them. There’s no template or format and no one has ever submitted one in the 10 years she was in this role.&lt;/p&gt;

&lt;p&gt;Wow, ok. I go ahead and tell her I’ll write one and ask how to submit them.&lt;/p&gt;

&lt;p&gt;This is funny to me, so later that evening, I spend a few minutes and write up a justification email. It’s pretty bullshit and basically says “Window blinds open makes me happier, and thus more productive. Closed blinds reduce productivity and thus reduce organization impact. Yadda yadda yadda.”&lt;/p&gt;

&lt;p&gt;It’s maybe two paragraphs. She replies that my justification is insufficient and that the blinds will stay closed.&lt;/p&gt;

&lt;p&gt;This is now less funny. So I look her up in the company director. She is a random workgroup’s secretary and is a contractor who has been in the building forever. She has contracted for three different companies, but has always sat there. She schedules appointments and orders staples and stuff for about 20 of the hundreds of people in the building. She’s on a different floor than me. There’s five floors. Her group is entirely on a different floor.&lt;/p&gt;

&lt;p&gt;This is bizarre. With this knowledge, I resubmit a revised justification. She denies it again. I respond copying her contracting monitor or whatever you call the person who organizes the contract, pointing out this bizarre process. The contracting person is confused and responds that this is not in any contract, they don’t give a shit, and question why this is done.&lt;/p&gt;

&lt;p&gt;After a few days of email silence, the contracting person responds that the blinds will stay open. I never hear from Betty again.&lt;/p&gt;

&lt;p&gt;I moved out of that office about 5 years ago. The blinds stayed open while I was there.&lt;/p&gt;

&lt;p&gt;About a year or two ago, I was in that building and I walked by my old area to talk to some former co-workers and noticed the blinds were down.&lt;/p&gt;

&lt;p&gt;I gave a quick recap and the current occupant didn’t care and hadn’t noticed. But they did say that Betty was still there.&lt;/p&gt;

&lt;p&gt;I think people get really invested in a process they think is important, but is only important to them. A co-worker called this “building imaginary castles in the sky.”&lt;/p&gt;

&lt;p&gt;It was really cool to them, but meaningless to any observer. Sometimes it’s useful in the long run for something else, but hard to tell immediately.&lt;/p&gt;

&lt;p&gt;There’s confusion that gets compounded and you have real stuff wrapped around it. It seems like it may be bullshit. It makes me think how much of what I care about is just organizational lock-in and not really meaningful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: I found this post when I was cleaning out my phone and I think I wrote it in 2018. So I expect this story took place in 2013 or so. Given the current state of government shutdown and free time, I thought that it’s theme of people doing crazy stuff for unknown reasons was still relevant.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;This reminds me a bit of the &lt;a href="/communication/2021/06/business_urban_legends.html"&gt;Business Urban Legends post&lt;/a&gt; story about the chimpanzees who don’t know why they do things.&lt;/li&gt;
&lt;/ul&gt;</description><author>PREPEND</author><pubDate>Thu, 02 Oct 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">/organizations/2025/10/organizational-lockin.html</guid></item><item><title>2025–10–02: Luckfox Pico Mini</title><link>https://xnux.eu/log/#101</link><author>megi's PinePhone Development Log</author><pubDate>Thu, 02 Oct 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#101</guid></item><item><title>Protecting your (my) passport from liquid damage</title><link>http://blog.jgc.org/2025/10/protecting-your-my-passport-from-liquid.html</link><description>&lt;p&gt;After an unfortunate incident involving my passport and some alcohol-based hand gel in my bag, I decided I needed some sort of waterproof passport protection. But searching around for "passport wallets" mostly doesn't result in anything waterproof and all the wallets are large, bulky or ugly. But there is a really simple solution.&lt;/p&gt;&lt;p&gt;A 4 mil ziploc bag of the appropriate size.&amp;nbsp;&lt;/p&gt;&lt;p&gt;What's the appropriate size? Well, there's an ICAO standard for &lt;a href="https://en.wikipedia.org/wiki/Machine-readable_passport"&gt;machine-readable passports&lt;/a&gt;&amp;nbsp;which uses the &lt;a href="https://en.wikipedia.org/wiki/ISO/IEC_7810"&gt;ISO/IEC 7810 ID3&lt;/a&gt; size for the "passport booklet" (what you and I would call a passport).&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEgLySRY4aVNHQa_MeC0gABnFCWe1LOoCdQsN72fCaZNgZHeWz3sCYodLu98N29o6rXxYx0ZWja3or4nUTmyzMLqlV_jUvT_FgZwUO47CDag5dK2pxAxILKHKX5hQrnye1quGVA8Xlx_VBs00lphVUd9vHVOlD_CPVLajrse7X9wQllPFNiTq20oaA" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" src="https://blogger.googleusercontent.com/img/a/AVvXsEgLySRY4aVNHQa_MeC0gABnFCWe1LOoCdQsN72fCaZNgZHeWz3sCYodLu98N29o6rXxYx0ZWja3or4nUTmyzMLqlV_jUvT_FgZwUO47CDag5dK2pxAxILKHKX5hQrnye1quGVA8Xlx_VBs00lphVUd9vHVOlD_CPVLajrse7X9wQllPFNiTq20oaA=s16000" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;So, that's 125 x 88mm. Happily, a pretty common ziploc bag size is 10 x 15cm (or 100 x 150mm, if you prefer). That gives you an extra 12mm horizontally (so the passport will slip inside) and 25mm vertically (space for the passport and the area with the ziploc closure).&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I mentioned "4 mil" above. Switching to non-metric measures (which seem common for the thickness of plastic) that's four 1/1000s of an inch (note it's not "millimetres", it's "mils"). You also sometimes see plastic measured in microns (which is 1/1000 of a millimetre).&amp;nbsp;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;4 mil bags have a sturdiness to them that thinner plastics do not have. They are sometimes described as "extra thick". In the UK,&amp;nbsp;&lt;a href="https://www.amazon.co.uk/stores/page/498A797C-420D-4094-BBCC-ED81C35B21F6?"&gt;these folks&lt;/a&gt;&amp;nbsp;(not an affiliate link) sell through Amazon and have a variety of bags of the right size and thickness.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;And now my (new) passport is carefully protected.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhgnW03040pIdvXdwmrEY1uC1dV9lZ_WPWETdktlSm9W5ZzNdFmmvyABr9N-pk2HF1GpTKC7tN8MdREa9pLtBS0PZWr6CkuviQH8JW6MTFs_F07Y__JZqjvxXydQnkdXgsxGmF-zY3drlU_dUmRjt_2UD4UHhYKyBeQfqeEnnLsgqGg-ck5NhvJQ/s1554/passport-2.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhgnW03040pIdvXdwmrEY1uC1dV9lZ_WPWETdktlSm9W5ZzNdFmmvyABr9N-pk2HF1GpTKC7tN8MdREa9pLtBS0PZWr6CkuviQH8JW6MTFs_F07Y__JZqjvxXydQnkdXgsxGmF-zY3drlU_dUmRjt_2UD4UHhYKyBeQfqeEnnLsgqGg-ck5NhvJQ/w422-h640/passport-2.jpg" width="422" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Aside: it turns out I actually once owned a waterproof passport wallet. I was gifted a &lt;a href="https://sulgear.com/collections/passport-wallets"&gt;SÜLgear passport wallet&lt;/a&gt;&amp;nbsp;and lost the waterproof liner. The kind folks at SÜLgear pointed out that the waterproof liner was simply a 4" x 6", 4 mil ziploc bag (or, for those of the metric persuasion, 102 x 152mm).&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Wed, 01 Oct 2025 20:05:09 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/10/protecting-your-my-passport-from-liquid.html</guid></item><item><title>“Every Hard Drive I’ve Owned Has Been Larger Than All My Previous Ones Combined”</title><link>https://blog.nawaz.org/posts/2025/Sep/every-hard-drive-ive-owned-has-been-larger-than-all-my-previous-ones-combined/</link><description>&lt;p&gt;Back in 2004, I saw this Slashdot
&lt;a class="reference external" href="https://hardware.slashdot.org/comments.pl?sid=107236&amp;amp;cid=9124531"&gt;comment&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every hard drive I&amp;#8217;ve ever bought has been larger than all my
previous hard drives combined. And this is without even&amp;nbsp;trying.&lt;/p&gt;
&lt;p&gt;–&amp;nbsp;blancolioni&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Reflecting on this, I realized that this was also true of all the hard
drives I had ever …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Sun, 28 Sep 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Sep/every-hard-drive-ive-owned-has-been-larger-than-all-my-previous-ones-combined/</guid></item><item><title>Upcoming projects, Fall 2025 edition</title><link>https://mikewarot.blogspot.com/2025/09/upcoming-projects-fall-2025-edition.html</link><description>Upcoming projects include revisiting old ideas&lt;br /&gt;&lt;br /&gt;Bidirectional compilation - a compiler that can work backwards to enable radical refactoring, possibly into other languages. I'll start with Pascal, because it doesn't really do macros, which break reverse causality.
 
Memex - I want to create a markup language for existing documents that can actually bring an 80 year old idea to life. The main feature is the ability to browse the web and have it all captured and ready for sharing and making copies on demand, something IP lawyers and censors would abhor.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;LiveSQL - a database that offers subscriptions to changes, and temporal features. There was an old database back in the MS-DOS days that had the idea of generations of changes to resolve locking and other issues. Maybe that becomes part of this. The main feature is that you could have a query that gives you new versions of the database as others make changes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Setting up my own electronics repair shop. My friend who I help has been doing this since the 1950s, I need to get my own workbench and things up and running again, it's been decades since I've had my own shop, now that I'm retired, it's time to set it up.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;De-hoarding - too much stuff that I won't use, and my child doesn't need to worry about when we're gone. Part of the shop process should be finding someone else to inherit that stuff, and save my kid the grief of finding it a home.&lt;br /&gt;&lt;br /&gt;Multidimensional ratings - Imagine a way to rate things on a forum (just to make it easier to start out), but instead of up/down vote, you can make up your own dimensions as well... humour, is_it_political, spam, etc. There are things you really can't talk about because the moderation systems we use globally have to worry about spam of many forms, and nuance gets crushed. I'd like to help address that.&lt;br /&gt;&lt;/div&gt;</description><author>--Mike--</author><pubDate>Fri, 26 Sep 2025 09:12:04 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2025/09/upcoming-projects-fall-2025-edition.html</guid></item><item><title>Revised Alternative Self Driving Car Levels</title><link>http://blog.onepatchdown.net/autonomy/self-driving/technology/2025/09/25/revised-autonomy-levels/</link><description>&lt;p&gt;I’m not the SAE, I’m just some dude, but imo the current SAE levels for driving automation jump from “driver assistance” to “partial automation” to “conditional automation” without capturing the nuanced progression of capabilities that actually matter for safety and real-world deployment. I propose these more granular levels as a framework that better reflects the technical challenges and safety milestones in the journey toward full autonomy.&lt;/p&gt;

&lt;h2 id="level-0-no-automation"&gt;Level 0: No Automation&lt;/h2&gt;

&lt;p&gt;Human does everything&lt;/p&gt;

&lt;h2 id="level-1-speed-control"&gt;Level 1: Speed Control&lt;/h2&gt;

&lt;p&gt;Can maintain speed (cruise control)
Human controls steering and everything else&lt;/p&gt;

&lt;h2 id="level-2-speed--steering"&gt;Level 2: Speed + Steering&lt;/h2&gt;

&lt;p&gt;Can control speed AND steering simultaneously
Requires constant human supervision&lt;/p&gt;

&lt;h2 id="level-3-stay-in-lane-highway"&gt;Level 3: Stay-in-Lane Highway&lt;/h2&gt;

&lt;p&gt;Can stay in a single lane on the freeway
Handles stop-and-go traffic
Can come to complete stop and resume without intervention
Has basic object detection (car ahead stopped = stop)
CANNOT change lanes or route around obstacles
This is SAE Level 2 today, but it’s a meaningful milestone on the path to SAE Level 3/4.&lt;/p&gt;

&lt;h2 id="level-4-full-highway-navigation--basic-object-recognition"&gt;Level 4: Full Highway Navigation &amp;amp; Basic object recognition.&lt;/h2&gt;

&lt;p&gt;Can change lanes, merge, take exits
Can navigate around obstacles by changing lanes
Handles all normal highway scenarios autonomously
Can pull over safely if confused
Will stop instead of running into something.&lt;/p&gt;

&lt;h2 id="level-5-advanced-object-recognition"&gt;Level 5: Advanced Object Recognition&lt;/h2&gt;

&lt;p&gt;Can distinguish between critical and harmless obstacles (child vs plastic bag)
Understands object physics (ball rolling = child may follow)
Can navigate simple urban environments and intersections
Can handle parking lots&lt;/p&gt;

&lt;h2 id="level-6-predictive-behavior-modeling"&gt;Level 6: Predictive Behavior Modeling&lt;/h2&gt;

&lt;p&gt;Can identify and predict behavior of pedestrians, cyclists, children, animals
Understands social driving cues (hand waves, eye contact)
Can handle construction zones with human flaggers
Can navigate complex urban environments in good conditions&lt;/p&gt;

&lt;h2 id="level-7-agi-level-driving"&gt;Level 7: AGI-Level Driving&lt;/h2&gt;

&lt;p&gt;Functions in all weather conditions (heavy snow, fog, torrential rain)
Can violate traffic laws when necessary for safety
Makes complex ethical decisions about least-harm outcomes
Handles unmapped roads and novel situations
Matches or exceeds best human driver in all scenarios&lt;/p&gt;

&lt;h2 id="commentary"&gt;Commentary&lt;/h2&gt;
&lt;p&gt;Comma.ai very capabily drives me down the freeway with much less cognitive burden. If you spend long amounts of time driving, you should get one. If your car does not support it, upgrade your car. (I’m not being paid to say that, just a happy customer.) It doesn’t integrate with GPS so you’ll miss the freeway exit if you don’t pay attention.&lt;/p&gt;

&lt;p&gt;Meanwhile, Waymo is better than 4, but it’s unclear exactly how much further it is. From experience riding in them, the Waymo Driver is already at 5, and some parts of 6.&lt;/p&gt;

&lt;p&gt;The industry has come a long way. The argument over LIDAR or not; comma.ai’s abilit to drive on the freeway suggests LIDAR is not as critical as it was back in 2009 when Waymo was the Goolgle self-driving car project.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Thu, 25 Sep 2025 23:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/autonomy/self-driving/technology/2025/09/25/revised-autonomy-levels/</guid></item><item><title>Homelab Failure Domains</title><link>https://www.petekeen.net/homelab-failure-domains/</link><description>&lt;p&gt;Everything is in shambles.&lt;/p&gt;
        &lt;p&gt;One machine is dead and probably not coming back.
        Services are randomly scattered amongst the survivors.
        My Kubernetes project is kinda-sorta paused.&lt;/p&gt;
        &lt;p&gt;I'm unnerved and want to get it all sorted out, but I need to do some thinking out loud first.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;My infrastructure currently consists of:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;
        &lt;p&gt;Omicron, a Kubernetes cluster that is just barely functioning. It is currently running this site, VMSave, and basically nothing else. Consists of &lt;code&gt;hypnotoad&lt;/code&gt;, &lt;code&gt;crushinator&lt;/code&gt;, and a control plane VM running on a Wyse 5070 running Proxmox.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
        &lt;p&gt;Nimbus, a Kubernetes cluster that is not functioning at all. I tried building out a GitOps-driven cluster as my second attempt and everything was going swimmingly until &lt;code&gt;nibbler&lt;/code&gt;, a historically unreliable piece of hardware that when it works has more cores and memory that the rest of my infra combined, fell over again in yet another inexplicable way.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
        &lt;p&gt;Lrrr, a box with an Intel N150 and 32GB of memory running a VM on top of Proxmox that is hosting almost everything that was previously on the two Kubernetes clusters.&lt;/p&gt;
        &lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;This jumbled state of affairs is basically due to a series of impulsive hardware purchases and &amp;quot;oh that's neat, let's do that&amp;quot; infrastructure changes.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;Let's talk about &lt;em&gt;failure domains&lt;/em&gt;.&lt;/p&gt;
        &lt;p&gt;I think of a failure domain as a set of risks and mitigation strategies as applied to a particular instance of a service.&lt;/p&gt;
        &lt;p&gt;The canonical example in the software-as-a-service world is &amp;quot;production&amp;quot;, i.e. the instance of the service that the customers touch. The one that makes the money. The primary risk is the money going away if the service goes down.&lt;/p&gt;
        &lt;p&gt;A SaaS shop may have a staging environment, where changes get tested before they hit production. The main risk in staging is inconveniencing your coworkers, but the consequences of that to the company are much less impactful.&lt;/p&gt;
        &lt;p&gt;Each developer in then hopefully has one or more of their own environments in which to actually make the software. These are practially risk free to the company as a whole, only inconvenicing one developer if something goes awry.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;Overcomplicated home infrastructure doesn't map neatly into the same failure domains as a SaaS business, of course, but they still exist.&lt;/p&gt;
        &lt;p&gt;When I think about the users of the services in my home I imagine a sort of abstract &amp;quot;household delight&amp;quot; score.
        Points accrue implicitly when things are running fine and people are able to use the things I'm trying to provide.
        Points get deducted when they notice things aren't working or when they see me stomping around grumbling about full hard drives and boot errors.&lt;/p&gt;
        &lt;p&gt;By that logic I have three different failure domains (actually four but we'll get to that):&lt;/p&gt;
        &lt;ol&gt;
        &lt;li&gt;
        &lt;p&gt;Critical production. The absense of service would be immediately noticed and commented upon, often affecting the comfort of the occupants of the house. Examples: network, DNS, Home Assistant and friends, IoT coordinators.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
        &lt;p&gt;Production: The absense of service would be noticed eventually but even an extended outage wouldn't cause hardship. Examples: Jellyfin, Sonarr and friends, paperless-ngx.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
        &lt;p&gt;Lab: I'm the only one affected by things breaking in the lab. A playground for testing and fucking around.&lt;/p&gt;
        &lt;/li&gt;
        &lt;/ol&gt;
        &lt;p&gt;The fourth failure domain that doesn't neatly map into the above is production services for external users. VMSave and this site are the big ones but there are a few smaller things too.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;When I'm brutally honest with myself I have to recognize that the biggest common source of failure in every domain is me. Trying things, adding hardware, replacing software, messing around, testing in production.&lt;/p&gt;
        &lt;p&gt;Often my partner will remark &amp;quot;I don't understand how things just fail!&amp;quot; They usually don't. Failure is an immediate or delayed result of me changing something without considering the impact.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;So. What to do.&lt;/p&gt;
        &lt;p&gt;Obviously first I need to deliniate the lab from everything else. Separate hardware for sure, maybe even hide it all behind another router and subnet.&lt;/p&gt;
        &lt;p&gt;For production, one plan would be to just put everything critical and production on the one docker VM and let it be.
        The machine isn't struggling overall but Jellyfin isn't super great because the N150 doesn't quite have the oomph necessary to transcode some of the stuff we have in real time.&lt;/p&gt;
        &lt;p&gt;Another plan would be to split them onto two machines running docker VMs. This would reduce the churn on critical production and reduce the chances of a change messing things up.&lt;/p&gt;
        &lt;p&gt;Yet another plan would be to spin up a separate Kubernetes cluster for each, moving right along the overcomplicated continuum.&lt;/p&gt;
        &lt;p&gt;The thing is, Kubernetes makes sense to me now that I've worked with it in anger a little. I really think for my application it makes sense, and the problems with Nimbus come down to &lt;code&gt;nibbler&lt;/code&gt; being flaky and k8s trying to self-heal without enough resources available.&lt;/p&gt;
        &lt;p&gt;I don't know what to do about external production. My intent was to have it at home out of principle (or maybe out of spite) but it would probably be better to have it in an isolated cloud environment.&lt;/p&gt;
        &lt;p&gt;The one Docker VM is working ok, but it's mixing failure domains which makes me uncomfortable. For now, things are how they are and I can't let myself worry about it too much.&lt;/p&gt;
        &lt;p&gt;Links in the footer if you have comments or ideas. I'd love to hear them.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Thu, 25 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/homelab-failure-domains/</guid></item><item><title>The Year in Agents - AI Engineer Paris</title><link>https://www.swyx.io/aie-paris</link><description>&lt;p&gt;I gave a short address for the &lt;strong&gt;first AIE Paris conference&lt;/strong&gt; organized by Koyeb!&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Tue, 23 Sep 2025 17:46:13 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/aie-paris</guid></item><item><title>Using FZF and bash to quickly move about</title><link>https://jodavaho.io/posts/tab-complete-fzf-cd.html</link><description>&lt;h2 id="oh-look"&gt;Oh look&lt;/h2&gt;
&lt;p&gt;&amp;hellip; another advanced cli utility that is a rewrite + a tiny bit of functionality. And bonus, it&amp;rsquo;s in Rust!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=45342943"&gt;https://news.ycombinator.com/item?id=45342943&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This particular utility, &lt;code&gt;z&lt;/code&gt; was also posted last year. It&amp;rsquo;s part of the &amp;ldquo;Warp&amp;rdquo; shell, and one could be forgiven for feeling like a dusty left-behind with all these fancy shell features.
Thing is, bash is turing complete. You can do all this with a tiny bit of learning about how to use the shell you have, vs learning an entirely new shell.
Plus, you&amp;rsquo;ll never be lost when you find yourself on another machine that isn&amp;rsquo;t hyper-customized with your brittle setup!&lt;/p&gt;
&lt;h2 id="basic-example"&gt;Basic example&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you have &lt;code&gt;fzf&lt;/code&gt; installed. If you don&amp;rsquo;t, you&amp;rsquo;ll probably want it for many reasons. If you refuse, then see below, but this does make things much easier.
Try this: &lt;code&gt;cd $(find . -type d | fzf)&lt;/code&gt;. That will change to a directory using fzf, and honestly it&amp;rsquo;s quite functional as is. You could make a bash function from that and use it.&lt;/p&gt;
&lt;p&gt;Try it with preview! &lt;code&gt;cd $(find . type d | fzf --preview=&amp;quot;ls {} -l&amp;quot;)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="scale-up"&gt;Scale up&lt;/h2&gt;
&lt;p&gt;Slightly more advanced would be tab-complete for a z-like cd command.&lt;/p&gt;
&lt;p&gt;It would work like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make an alias fcd&lt;/li&gt;
&lt;li&gt;Make a tab complete that does that for the command fcd&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is kind of 101 bash - just DIY.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s mine:&lt;/p&gt;
&lt;p&gt;(2) is the hardest part - just write something that works with &lt;code&gt;complete&lt;/code&gt; and fzf. Nowadays this is childs play for any AI to just spit out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;fz_comp&lt;span style="color: #f92672;"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  COMPREPLY&lt;span style="color: #f92672;"&gt;=()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  local cur&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;${&lt;/span&gt;COMP_WORDS[COMP_CWORD]&lt;span style="color: #e6db74;"&gt;}&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  local prev&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;${&lt;/span&gt;COMP_WORDS[COMP_CWORD-1]&lt;span style="color: #e6db74;"&gt;}&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  local opts&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"none"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; &lt;span style="color: #f92672;"&gt;[&lt;/span&gt; -z &lt;span style="color: #e6db74;"&gt;"&lt;/span&gt;$cur&lt;span style="color: #e6db74;"&gt;"&lt;/span&gt; &lt;span style="color: #f92672;"&gt;]&lt;/span&gt;;&lt;span style="color: #66d9ef;"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    COMPREPLY&lt;span style="color: #f92672;"&gt;=(&lt;/span&gt;&lt;span style="color: #66d9ef;"&gt;$(&lt;/span&gt;find $1 -type d | fzf --preview&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"ls {} -l"&lt;/span&gt;&lt;span style="color: #66d9ef;"&gt;)&lt;/span&gt;&lt;span style="color: #f92672;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #66d9ef;"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  COMPREPLY&lt;span style="color: #f92672;"&gt;=(&lt;/span&gt;&lt;span style="color: #66d9ef;"&gt;$(&lt;/span&gt;find $1 -type d | grep $cur | fzf --preview&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"ls {} -l"&lt;/span&gt;&lt;span style="color: #66d9ef;"&gt;)&lt;/span&gt;&lt;span style="color: #f92672;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(1) is just a) set the new command b) make the completion call c) map that call to  completion.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;alias fcd&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;cd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;_fcd&lt;span style="color: #f92672;"&gt;(){&lt;/span&gt; fz_comp &lt;span style="color: #66d9ef;"&gt;$(&lt;/span&gt;pwd&lt;span style="color: #66d9ef;"&gt;)&lt;/span&gt; &lt;span style="color: #f92672;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;complete -F _fcd fcd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;there you go. Now you can &lt;code&gt;fcd &amp;lt;tab&amp;gt;&lt;/code&gt; to get fuzzy search on directories to change to.&lt;/p&gt;
&lt;h2 id="extending"&gt;Extending&lt;/h2&gt;
&lt;p&gt;Note this: &lt;code&gt;fz_comp $(pwd)&lt;/code&gt;&amp;hellip; This sets the current directory (&lt;code&gt;$(pwd)&lt;/code&gt;) to be the &lt;em&gt;root&lt;/em&gt; of the search. You can pick any directory. I have several aliases like &lt;code&gt;fcd&lt;/code&gt; to change to my work root workspace, or my journal, or anywhere else.&lt;/p&gt;
&lt;p&gt;Just add many commands like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;# Change to a subfolder of .logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;alias lcd&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;cd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;_lcd&lt;span style="color: #f92672;"&gt;(){&lt;/span&gt; fz_comp ~/.logs &lt;span style="color: #f92672;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;complete -F _lcd lcd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;# Change to a subfolder of .journal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;alias jcd&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;cd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;_jcd&lt;span style="color: #f92672;"&gt;(){&lt;/span&gt; fz_comp ~/.journal &lt;span style="color: #f92672;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;complete -F _jcd jcd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;etc&amp;hellip;&lt;/p&gt;
&lt;h2 id="no-fzf-for-idealogical-reasons"&gt;No fzf for idealogical reasons?&lt;/h2&gt;
&lt;p&gt;OK, so you want to fight about fzf? That&amp;rsquo;s fine, try it &lt;a href="https://github.com/jodavaho/smartcd"&gt;this way (github.com/jodavaho/smartcd)&lt;/a&gt; which is pure bash and easily installed. Of course, I wrote it by hand long ago when I was too dumb to use fzf, and it&amp;rsquo;s in serious need of cleanup, but there you go.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Tue, 23 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/tab-complete-fzf-cd.html</guid></item><item><title>Is a movie prop the ultimate laptop bag?</title><link>http://blog.jgc.org/2025/09/is-movie-prop-ultimate-laptop-bag.html</link><description>&lt;p&gt;A while back I attended a &lt;a href="https://blog.cloudflare.com/three-chapters-at-cloudflare-programmer-to-cto-to-board-of-directors/"&gt;Cloudflare board meeting&lt;/a&gt;&amp;nbsp;and carried my laptop in a brown paper bag from a grocery store. This elicited a few comments about needing a real bag. But I've often carried my laptop in inconspicuous bags. For me, the ultimate laptop bag is one that looks nothing like a laptop bag; it should look like nothing special at all.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Unhappily, grocery bags are not very strong, but there is a solution.&lt;/p&gt;&lt;p&gt;Movie prop grocery bags like this:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLCsTiYJtgILVWSW6NUSgg7N1Auc969GWG9_LApQNtXRhRHsJwid4jLQ1j8OEAH4YVfeLi0El-jci5YtdUMyf4f-QEcGPZWYF05y9M0On0vskk9y0jFi8ziaXLpQVJ5_1dgiJcCcFO7OoI3c69NTQNCT_n3UM8go30Fry57Lr2ruGsaA1LnKJZVw/s5330/bag-1.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="438" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLCsTiYJtgILVWSW6NUSgg7N1Auc969GWG9_LApQNtXRhRHsJwid4jLQ1j8OEAH4YVfeLi0El-jci5YtdUMyf4f-QEcGPZWYF05y9M0On0vskk9y0jFi8ziaXLpQVJ5_1dgiJcCcFO7OoI3c69NTQNCT_n3UM8go30Fry57Lr2ruGsaA1LnKJZVw/w640-h438/bag-1.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Looks like a classic brown paper grocery bag, but it isn't. It's actually a &lt;a href="https://www.dependableexpendables.com/search?type=product&amp;amp;q=silent+bag+props+"&gt;silent prop&lt;/a&gt; made for a film set where the noise from a crinkling paper bag would overwhelm the dialogue.&amp;nbsp;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;p&gt;Instead of being made from paper, it's made from fabric and is more robust then an actual paper bag. It's also nearly silent (well, much, much quieter than the real thing).&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKbYJ4h6bF_u6NoZjHYbCchtfwOO_sBPp8E7TOLrL1yhSigbzyGH1iXuH0jP1CjcqDTxewHa6xnLFd8dSVbgvE_gb47w-mmwoURZkmdUKEwg7iZ2j6uxyvivF-6PDvxWLWCTT7Z9PT_xKP299HT7KDgd3oj23K-Rq8hZQC_aD0NoKiINFpqelmfA/s5212/bag-2.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKbYJ4h6bF_u6NoZjHYbCchtfwOO_sBPp8E7TOLrL1yhSigbzyGH1iXuH0jP1CjcqDTxewHa6xnLFd8dSVbgvE_gb47w-mmwoURZkmdUKEwg7iZ2j6uxyvivF-6PDvxWLWCTT7Z9PT_xKP299HT7KDgd3oj23K-Rq8hZQC_aD0NoKiINFpqelmfA/w640-h432/bag-2.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;It's also the ideal size for my trusty and &lt;a href="https://blog.jgc.org/2024/01/my-daily-driver-is-older-than-i-thought.html"&gt;vintage&lt;/a&gt; MacBook Pro. Especially when it's in its sleeve.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8rGk6yij-QoN-rUsODKnFMQ3Vqd5bQNn3RBJdIBJHnJ_SscBE1esArQHYhqhMt4rg9qMRkgUSXa3RHw20kkAvL_c25BJeRKnvmHFnDGRcBx5Vm8fYfJSS4edF0iIPPgn29HTHeCc1EwPoAHpFlBkvPOPiRApNLPfJLIjGOdwlJRZeEPHXyGIaQA/s4924/bag-3.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="438" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8rGk6yij-QoN-rUsODKnFMQ3Vqd5bQNn3RBJdIBJHnJ_SscBE1esArQHYhqhMt4rg9qMRkgUSXa3RHw20kkAvL_c25BJeRKnvmHFnDGRcBx5Vm8fYfJSS4edF0iIPPgn29HTHeCc1EwPoAHpFlBkvPOPiRApNLPfJLIjGOdwlJRZeEPHXyGIaQA/w640-h438/bag-3.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;So, is a movie prop the ultimate laptop bag? No, clearly &lt;a href="https://en.wikipedia.org/wiki/Betteridge%27s_law_of_headlines"&gt;Betteridge's Law of Headlines&lt;/a&gt; applies, but it does fit my criteria for discretion and &lt;a href="https://news.ycombinator.com/item?id=45338980"&gt;not being ostentatious&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 22 Sep 2025 23:54:25 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/09/is-movie-prop-ultimate-laptop-bag.html</guid></item><item><title>Dear GitHub: no YAML anchors, please</title><link>https://blog.yossarian.net/2025/09/22/dear-github-no-yaml-anchors</link><description>TL;DR: for a very long time, GitHub Actions lacked support for YAML anchors.</description><author>ENOSUCHBLOG</author><pubDate>Mon, 22 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/09/22/dear-github-no-yaml-anchors</guid></item><item><title>I will remember Charlie Kirk</title><link>https://blog.rongarret.info/2025/09/i-will-remember-charlie-kirk.html</link><description>I don't watch right-wing media personalities much because I find it too painful.&amp;nbsp; For that matter, I don't watch left-wing media personalities much either for the same reason.&amp;nbsp; But the pain in each case arises from very different sources.&amp;nbsp; The right-wing pain comes from being weary of the lies and the distortions and the brazen hypocrisy.&amp;nbsp; The left-wing pain comes from</description><author>Rondam Ramblings</author><pubDate>Thu, 18 Sep 2025 01:00:10 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/09/i-will-remember-charlie-kirk.html</guid></item><item><title>Why Is Xcode So Antagonistic To Reduced Vision?</title><link>https://thecodist.com/why-is-xcode-so-antagonistic-to-reduced-vision/</link><description>&lt;p&gt;My eyesight is generally OK, but I have trouble reading small text, especially medium gray on light gray backgrounds. I usually code in 16 or even 18 point fonts, currently Atkinson Hyperlegible Mono. Every new release of Xcode makes seeing various UI elements harder, as if the designers all have&lt;/p&gt;</description><author>The Codist</author><pubDate>Thu, 18 Sep 2025 00:31:34 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/why-is-xcode-so-antagonistic-to-reduced-vision/</guid></item><item><title>We are starting a company to rethink email</title><link>https://austinhenley.com/blog/startingacompany.html</link><description>&lt;a href="https://austinhenley.com/blog/startingacompany.html"&gt;https://austinhenley.com/blog/startingacompany.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Mon, 15 Sep 2025 21:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/startingacompany.html</guid></item><item><title>In response to a developer asking about systems</title><link>http://notes.eatonphil.com/2025-09-15-what-is-systems-and-how-do-i-learn.html</link><description>&lt;p class="note"&gt;
  Sometimes I get asked questions that would be more fun to answer in
  public. All letters are treated as anonymous unless permission is
  otherwise granted.
&lt;/p&gt;&lt;p&gt;Hey [Redacted]! It's great to hear from you. I'm very glad you joined
the &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;coffee club&lt;/a&gt;
and met some good folks. :)&lt;/p&gt;
&lt;p&gt;You asked how to learn about systems. A great question! I think I need
to start first with what I mean when I say systems.&lt;/p&gt;
&lt;p&gt;My definition of systems is all of the underlying software we
developers use but are taught not to think about because they are so
solid: our compilers and interpreters, our databases, our operating
system, our browser, and so on. We think of them as basically not
having bugs, we just count on them to be correct and fast enough so we
can build the applications that really matter to users.&lt;/p&gt;
&lt;p&gt;But 1) some developers do actually have to work on these fundamental
blocks (compilers, databases, operating systems, browsers, etc.) and
2) it's not thaaaat hard to get into this development professionally
and 3) even if you don't get into it professionally, having a better
understanding of these fundamental blocks will make you a better
application developer. At least I think so.&lt;/p&gt;
&lt;p&gt;To get into systems I think it starts by you just questioning how each
layer you build on works. Try building that layer yourself. For
example you've probably used a web framework like Rails or
Next.js. But you can just go and write that layer yourself too (for
education).&lt;/p&gt;
&lt;p&gt;And you've probably used Postgres or SQLite or DynamoDB. But you can
also just go and write that layer yourself (for education). It's this
habit of thinking and digging into the next lower layer that will get
you into systems. Basically, not being satisfied with the black box.&lt;/p&gt;
&lt;p&gt;I do not think there are many good books on programming in general,
and very very few must-read ones, but one that I recommend to
everybody is Designing Data Intensive Applications. I think it's best
if you read it with a group of people. (My &lt;a href="https://eatonphil.com/bookclub.html"&gt;book club&lt;/a&gt; will read it in
December when the 2nd edition comes out, you should join.) But this
book is specific to data obviously and not interested in the
fundamentals of other systems things like compilers or operating
systems or browsers or so on.&lt;/p&gt;
&lt;p&gt;Also, I see getting into this as a long-term thing. Throughout my
whole career (almost 11 years now) I definitely always tried to dig
into compilers and interpreters, I wrote and blogged about toy
implementations a lot. And then 5 years ago I started digging into
databases and saw that there was more career potential there. But it
still took 4 years &lt;a href="https://notes.eatonphil.com/2025-02-15-from-web-developer-to-database-developer-in-10-years.html"&gt;until I got my first
job&lt;/a&gt;
as a developer working on a database (the job I currently have).&lt;/p&gt;
&lt;p&gt;Things take time to learn and that's ok! You have a long career to
look forward to. And if you end up not wanting to dig into this stuff
that's totally fine too. I think very few developers actually do. And
they still have fine careers.&lt;/p&gt;
&lt;p&gt;Anyway, I hope this is at least mildly useful. I hope you join the
&lt;a href="https://eatonphil.com/discord.html"&gt;Software Internals Discord&lt;/a&gt; and
&lt;a href="https://nycsystems.xyz/"&gt;nycsystems.xyz&lt;/a&gt; as well and look forward to
seeing you at future coffee clubs!&lt;/p&gt;
&lt;p&gt;Cheers,&lt;br /&gt;
Phil&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a letter in response to a developer asking about how to learn systems. &lt;a href="https://t.co/2ILNpzl662"&gt;pic.twitter.com/2ILNpzl662&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1967593932684181895?ref_src=twsrc%5Etfw"&gt;September 15, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 15 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-09-15-what-is-systems-and-how-do-i-learn.html</guid></item><item><title>a few notes on ratelimiting</title><link>https://dotat.at/@/2025-09-14-ratelimit.html</link><description>&lt;p&gt;Last year I wrote a pair of articles about ratelimiting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2024-08-30-gcra.html"&gt;GCRA: leaky buckets without the buckets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2024-09-02-ewma.html"&gt;exponential rate limiting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recently, Chris “cks” Siebenmann has been &lt;a href="https://utcc.utoronto.ca/~cks/space/blog/programming/RatelimitExponentialNotes?showcomments"&gt;working on
ratelimiting&lt;/a&gt; HTTP bots that are hammering his blog. His articles
prompted me to write some clarifications, plus a few practical
anecdotes about ratelimiting email.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-14-ratelimit.html#mea-culpa" name="mea-culpa"&gt;mea culpa&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The main reason I wrote the GCRA article was to explain GCRA better
without the standard obfuscatory terminology, and to compare GCRA with
a non-stupid version of the leaky bucket algorithm. It wasn’t written
with my old exponential ratelimiting in mind, so I didn’t match up the
vocabulary.&lt;/p&gt;
&lt;p&gt;In the exponential ratelimiting article I tried to explain how the
different terms correspond to the same ideas, but I botched it by
trying to be too abstract. So let’s try again.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-14-ratelimit.html#parameters" name="parameters"&gt;parameters&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It’s simplest to configure these ratelimiters (leaky bucket, GCRA,
exponential) with two parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;limit&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;period&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The maximum permitted average &lt;em&gt;rate&lt;/em&gt; is calculated from these
parameters by dividing them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;rate&lt;/em&gt; = &lt;em&gt;limit / period&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;em&gt;period&lt;/em&gt; is the time over which client behaviour is averaged,
which is also how long it takes for the ratelimiter to forget past
behaviour. In &lt;a href="https://dotat.at/@/2024-08-30-gcra.html"&gt;my GCRA article&lt;/a&gt; I called it the &lt;em&gt;window&lt;/em&gt;. Linear
ratelimiters (leaky bucket and GCRA) are 100% forgetful after one
period; the exponential ratelimiter is 67% forgetful.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;limit&lt;/em&gt; does double duty: as well as setting the maximum average
rate (measured in requests per period) it sets the maximum size
(measured in requests) of a fast &lt;em&gt;burst&lt;/em&gt; of requests following a
sufficiently long quiet gap.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-14-ratelimit.html#how-bursty" name="how-bursty"&gt;how bursty&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can increase or decrease the burst limit – while keeping the
average rate limit the same – by increasing or decreasing both the
&lt;em&gt;limit&lt;/em&gt; and the &lt;em&gt;period&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For example, I might set &lt;em&gt;limit&lt;/em&gt; = 600 requests per &lt;em&gt;period&lt;/em&gt; = 1 hour.
If I want to allow the same average rate, but with a smaller burst
size, I might set &lt;em&gt;limit&lt;/em&gt; = 10 requests per &lt;em&gt;period&lt;/em&gt; = 1 minute.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-09-14-ratelimit.html#anecdote" name="anecdote"&gt;anecdote&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When I was looking after email servers, I set ratelimits for
departmental mail servers to catch outgoing spam in case of
compromised mail accounts or web servers. I sized these limits to a
small multiple of the normal traffic so that legitimate mail was not
delayed but spam could be stopped fairly quickly.&lt;/p&gt;
&lt;p&gt;A typical setting was 200/hour, which is enough for a medium-sized
department. (As a rule of thumb, expect people to send about 10
messages per day.)&lt;/p&gt;
&lt;p&gt;An hourly limit is effective at catching problems quickly during
typical working hours, but it can let out a lot of spam over a
weekend.&lt;/p&gt;
&lt;p&gt;So I would also set a second backstop limit like 1000/day, based on
average daily traffic instead of peak hourly traffic. It’s a lower
average rate that doesn’t forget bad behaviour so quickly, both of
which help with weekend spam.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-14-ratelimit.html#variable-cost" name="variable-cost"&gt;variable cost&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Requests are not always fixed-cost. For example, you might want to
count the request size in bytes when ratelimiting bandwidth.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dotat.at/@/2024-09-02-ewma.html"&gt;The exponential algorithm&lt;/a&gt; calculates the instantaneous rate as&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    r_inst = cost / interval
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;em&gt;cost&lt;/em&gt; is the size of the request and &lt;em&gt;interval&lt;/em&gt; is the time
since the previous request relative to the period.&lt;/p&gt;
&lt;p&gt;I’ve edited &lt;a href="https://dotat.at/@/2024-08-30-gcra.html"&gt;my GCRA algorithm&lt;/a&gt; to make the cost of requests
more clear. In GCRA a request uses up some part of the client’s
window, a nominal time measured in seconds. To convert a request’s
size into time spent:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    spend = cost / rate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the client’s earliest permitted time should be updated like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    time += cost * period / limit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(In constrained implementations the &lt;em&gt;period / limit&lt;/em&gt; factor can be
precomputed.)&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-14-ratelimit.html#how-lenient" name="how-lenient"&gt;how lenient&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When a client has used up its burst limit and is persistently making
requests faster than its rate limit, the way the ratelimiter accepts
or rejects requests is affected by the way it updates its memory of
the client.&lt;/p&gt;
&lt;p&gt;For &lt;a href="https://www.exim.org/exim-html-current/doc/html/spec_html/ch-access_control_lists.html#SECTratelimiting"&gt;exim’s ratelimit feature&lt;/a&gt; I provided two modes called
“strict” and “leaky”. There is a third possibility: an intermediate
mode which I will call “forgiving”.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The “leaky” mode is most lenient. An over-limit client will have
occasional requests accepted at the maximum permitted rate. The
rest of its requests will be rejected.&lt;/p&gt;
&lt;p&gt;When a request is accepted, all of the client’s state is updated;
when a request is rejected, the client’s state is left unchanged.&lt;/p&gt;
&lt;p&gt;The lenient leaky mode works for both GCRA and exponential
ratelimiting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In “forgiving” mode, all of a client’s requests are rejected while
it is over the ratelimit. As soon as it slows down below the
ratelimit its requests will start being accepted.&lt;/p&gt;
&lt;p&gt;When a request is accepted, all of the client’s state is updated;
when a request is rejected, the client’s time is updated, but (in
the exponential ratelimiter) not its measured rate.&lt;/p&gt;
&lt;p&gt;The forgiving mode works for both GCRA and exponential
ratelimiting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In “strict” mode, all of a client’s requests are rejected while it
is over the ratelimit, and requests continue to be rejected after
a client has slowed down depending on how fast it previously was.&lt;/p&gt;
&lt;p&gt;When a request is accepted or rejected, both of the client’s time
and measured rate are updated.&lt;/p&gt;
&lt;p&gt;The strict mode only works for exponential ratelimiting.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I only realised yesterday, from the discussion with cks, how a
“forgiving” mode can be useful for the exponential ratelimiter, and
how it corresponds to the less-lenient mode of linear leaky bucket and
GCRA ratelimiters. (I didn’t describe the less-lenient mode in my GCRA
article.)&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-09-14-ratelimit.html#anecdote" name="anecdote"&gt;anecdote&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the hardest things about ratelimiting email was coming up with
a policy that didn’t cause undue strife and unnecessary work.&lt;/p&gt;
&lt;p&gt;When other mail servers (like the departmental server in the anecdote
above) were sending mail through my relays, it made sense to use
“leaky” ratelimiter mode with SMTP 450 temporary rejects. When there
was a flood of mail, messages would be delayed and retried
automatically.&lt;/p&gt;
&lt;p&gt;When their queue size alerts went off, the department admin could take
a look and respond as appropriate.&lt;/p&gt;
&lt;p&gt;That policy worked fairly well.&lt;/p&gt;
&lt;p&gt;However, when the sender was an end-user sending via my MUA message
submission servers, they usually were not running software that could
gracefully handle an SMTP 450 temporary rejection.&lt;/p&gt;
&lt;p&gt;The most difficult cases were the various college and department
alumni offices. Many of them would send out annual newsletters, using
some dire combination of Microsoft Excel / Word / Outlook mailmerge,
operated by someone with limited ability to repair a software failure.&lt;/p&gt;
&lt;p&gt;In that situation, SMTP 450 errors broke their mailshots, causing
enormous problems for the alumni office and their local IT support.
(Not nice to realise I caused such trouble!)&lt;/p&gt;
&lt;p&gt;The solution was to configure the ratelimiter in “strict” mode and
“freeze” or quarantine over-limit bulk mail from MUAs. The “strict”
mode ensured that everything after the initial &lt;em&gt;burst&lt;/em&gt; of a spam run
was frozen.&lt;/p&gt;
&lt;p&gt;When the alert was triggered I inspected a sample of the frozen
messages. If they were legitimate newsletters, I could thaw them for
delivery and reset the user’s ratelimit. In almost all cases the user
would not be disturbed.&lt;/p&gt;
&lt;p&gt;If it turned out the user’s account was compromised and used to send
spam, then I could get their IT support to help sort it out, and
delete the frozen junk from the quarantine.&lt;/p&gt;
&lt;p&gt;That policy worked OK: I was the only one who had to deal with my own
false positives, and they were tolerably infrequent.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Sun, 14 Sep 2025 06:30:44 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-09-14-ratelimit.html</guid></item><item><title>One year of zizmor</title><link>https://blog.yossarian.net/2025/09/14/one-year-of-zizmor</link><description>This is a dual purpose post: I’ve released zizmor v1.13.0, and zizmor has turned one year old1! It depends on how you count: zizmor’s first commit was roughly &amp;#8617;</description><author>ENOSUCHBLOG</author><pubDate>Sun, 14 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/09/14/one-year-of-zizmor</guid></item><item><title>On Christmas Eve, Daniel Stenberg called my code bad</title><link>https://jodavaho.io/posts/dev-curl-pebkac.html</link><description>&lt;p&gt;I found out today that &lt;a href="https://github.com/bagder"&gt;Daniel Stenberg&lt;/a&gt; the guy behind curl, which is the
library behind basically every web-enabled tool you use, was nominated
developer of the year in Sweden. Now, honestly, this guy works &lt;em&gt;hard&lt;/em&gt;, and
curl represents the premier F/OSS experience. It&amp;rsquo;s free, ubiquitous, actively
maintained, and essentially vital infrastructure.&lt;/p&gt;
&lt;p&gt;So, here&amp;rsquo;s a story about how he called my code bad.&lt;/p&gt;
&lt;p&gt;Christmas eve, 2021, I had a little time off and was enjoying some casual coding. I&amp;rsquo;ve always loved C over C++, which at the time was my &amp;ldquo;day job&amp;rdquo; language.&lt;/p&gt;
&lt;p&gt;So there I was nestled in, waiting for dinner finish, likely over at my family&amp;rsquo;s house listening to the siblings, nephews, etc bicker and play.&lt;/p&gt;
&lt;p&gt;I was working my way through the curl book. I even found and submitted a teeny tiny fix! (&lt;a href="https://github.com/curl/everything-curl/pull/172/files"&gt;github link&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;My usual way to learn from docs is to &amp;ldquo;paraphrase&amp;rdquo; the code examples, re-developing them by hand rather than copy-pasting them.&lt;/p&gt;
&lt;p&gt;So, I had done that, but was constantly finding a segfault. In some OSS projects, questions are sometimes welcome on github. It being Christmas eve, I figured I would try to formulate a well-structured question and check in after a few days.&lt;/p&gt;
&lt;p&gt;Of course, nearly immediately, two curl maintainers, including Daniel Stenberg himself, pointed out my dumb mistake. You can &lt;a href="https://github.com/curl/curl/issues/8179#issuecomment-1000941889"&gt;read for yourself&lt;/a&gt;, or see this reply:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bad Code" src="https://jodavaho.io/img/curl-bad-code.png" /&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not proud - that was bad code. But, it happens to us all, and C++ poisons your respect for api types with implicit type coercion, which is why I try to work only in C or Rust, but that is a conversation for another day.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Sat, 13 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dev-curl-pebkac.html</guid></item><item><title>What 4chan is up to since Charlie Kirk was murdered</title><link>https://caseysoftware.com/blog/what-4chan-is-up-to-since-charlie-kirk-was-murdered?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=what-4chan-is-up-to-since-charlie-kirk-was-murdered</link><description>&lt;p&gt;Disclosure: I&amp;#8217;m not involved in 4chan in any way but I have deep fear appreciation for the weaponized autists. What&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/what-4chan-is-up-to-since-charlie-kirk-was-murdered"&gt;What 4chan is up to since Charlie Kirk was murdered&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Fri, 12 Sep 2025 18:11:59 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/what-4chan-is-up-to-since-charlie-kirk-was-murdered?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=what-4chan-is-up-to-since-charlie-kirk-was-murdered</guid></item><item><title>first-class merges and cover letters</title><link>https://dotat.at/@/2025-09-11-cover-letter.html</link><description>&lt;p&gt;Although it looks really good, I have not yet tried &lt;a href="https://jj-vcs.github.io/jj/latest/"&gt;the Jujutsu (jj)
version control system&lt;/a&gt;, mainly
because it’s not yet clearly superior to &lt;a href="https://magit.vc"&gt;Magit&lt;/a&gt;.
But I have been following jj discussions with great interest.&lt;/p&gt;
&lt;p&gt;One of the things that jj has not yet tackled is how to do better than
git refs / branches / tags. As I underestand it, jj currently has
something like Mercurial bookmarks, which are more like raw git ref
plumbing than a high-level porcelain feature. In particular, jj lacks
signed or annotated tags, and it doesn’t have branch names that
always automatically refer to the tip.&lt;/p&gt;
&lt;p&gt;This is clearly a temporary state of affairs because jj is still
incomplete and under development and these gaps are going to be
filled. But the discussions have led me to think about how git’s
branches are unsatisfactory, and what could be done to improve them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#branch"&gt;branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#merge"&gt;merge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#rebase"&gt;rebase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#squash"&gt;squash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#fork"&gt;fork&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#cover-letters"&gt;cover letters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#previous-branch"&gt;previous branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#workflow"&gt;workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#questions"&gt;questions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#branch" name="branch"&gt;branch&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the huge improvements in git compared to Subversion was git’s
support for merges. Subversion proudly advertised its support for
lightweight branches, but a branch is not very useful if you can’t
merge it: an un-mergeable branch is not a tool you can use to help
with work-in-progress development.&lt;/p&gt;
&lt;p&gt;The point of this anecdote is to illustrate that rather than trying to
make branches better, we should try to make merges better and branches
will get better as a consequence.&lt;/p&gt;
&lt;p&gt;Let’s consider a few common workflows and how git makes them all
unsatisfactory in various ways. Skip to &lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#cover-letters"&gt;cover
letters&lt;/a&gt; and &lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#previous-branch"&gt;previous branch&lt;/a&gt; below
where I eventually get to the point.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#merge" name="merge"&gt;merge&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A basic merge workflow is,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create a feature branch&lt;/li&gt;
&lt;li&gt;hack, hack, review, hack, approve&lt;/li&gt;
&lt;li&gt;merge back to the trunk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The main problem is when it comes to the merge, there may be conflicts
due to concurrent work on the trunk.&lt;/p&gt;
&lt;p&gt;Git encourages you to resolve conflicts while creating the merge
commit, which tends to bypass the normal review process. Git also
gives you an ugly useless canned commit message for merges, that hides
what you did to resolve the conflicts.&lt;/p&gt;
&lt;p&gt;If the feature branch is a linear record of the work then it can be
cluttered with commits to address comments from reviewers and to fix
mistakes. Some people like an accurate record of the history, but
others prefer the repository to contain clean logical changes that
will make sense in years to come, keeping the clutter in the code
review system.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#rebase" name="rebase"&gt;rebase&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A rebase-oriented workflow deals with the problems of the merge
workflow but introduces new problems.&lt;/p&gt;
&lt;p&gt;Primarily, rebasing is intended to produce a tidy logical commit
history. And when a feature branch is rebased onto the trunk before it
is merged, a simple fast-forward check makes it trivial to verify that
the merge will be clean (whether it uses separate merge commit or
directly fast-forwards the trunk).&lt;/p&gt;
&lt;p&gt;However, it’s hard to compare the state of the feature branch before
and after the rebase. The current and previous tips of the branch
(amongst other clutter) are recorded in the reflog of the person who
did the rebase, but they can’t share their reflog. A force-push erases
the previous branch from the server.&lt;/p&gt;
&lt;p&gt;Git forges sometimes make it possible to compare a branch before and
after a rebase, but it’s usually very inconvenient, which makes it
hard to see if review comments have been addressed. And a reviewer
can’t fetch past versions of the branch from the server to review them
locally.&lt;/p&gt;
&lt;p&gt;You can mitigate these problems by adding commits in &lt;code&gt;--autosquash&lt;/code&gt;
format, and delay rebasing until just before merge. However that
reintroduces the problem of merge conflicts: if the autosquash doesn’t
apply cleanly the branch should have another round of review to make
sure the conflicts were resolved OK.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#squash" name="squash"&gt;squash&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When the trunk consists of a sequence of merge commits, the
&lt;code&gt;--first-parent&lt;/code&gt; log is very uninformative.&lt;/p&gt;
&lt;p&gt;A common way to make the history of the trunk more informative, and
deal with the problems of cluttered feature branches and poor rebase
support, is to squash the feature branch into a single commit on the
trunk instead of mergeing.&lt;/p&gt;
&lt;p&gt;This encourages merge requests to be roughly the size of one commit,
which is arguably a good thing. However, it can be uncomfortably
confining for larger features, or cause extra busy-work co-ordinating
changes across multiple merge requests.&lt;/p&gt;
&lt;p&gt;And squashed feature branches have the same merge conflict problem as
rebase &lt;code&gt;--autosquash&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#fork" name="fork"&gt;fork&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Feature branches can’t always be short-lived. In the past I have
maintained local hacks that were used in production but were not (not
yet?) suitable to submit upstream.&lt;/p&gt;
&lt;p&gt;I have tried keeping a stack of these local patches on a git branch
that gets rebased onto each upstream release. With this setup the
problem of reviewing successive versions of a merge request becomes
the bigger problem of keeping track of how the stack of patches
evolved over longer periods of time.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#cover-letters" name="cover-letters"&gt;cover letters&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cover letters are common in the email patch workflow that predates
git, and they are supported by git format-patch. Github and other
forges have a webby version of the cover letter: the message that
starts off a pull request or merge request.&lt;/p&gt;
&lt;p&gt;In git, cover letters are second-class citizens: they aren’t stored in
the repository. But many of the problems I outlined above have neat
solutions if cover letters become first-class citizens, with a Jujutsu
twist.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A first-class cover letter starts off as a prototype for a merge
request, and becomes the eventual merge commit.&lt;/p&gt;
&lt;p&gt;Instead of unhelpful auto-generated merge commits, you get helpful
and informative messages. No extra work is needed since we’re
already writing cover letters.&lt;/p&gt;
&lt;p&gt;Good merge commit messages make good &lt;code&gt;--first-parent&lt;/code&gt; logs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The cover letter subject line works as a branch name. No more need
to invent filename-compatible branch names!&lt;/p&gt;
&lt;p&gt;Jujutsu doesn’t make you name branches, giving them random names
instead. It shows the subject line of the topmost commit as a
reminder of what the branch is for. If there’s an explicit cover
letter the subject line will be a better summary of the branch as
a whole.&lt;/p&gt;
&lt;p&gt;I often find the last commit on a branch is some post-feature
cleanup, and that kind of commit has a subject line that is never
a good summary of its feature branch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As a prototype for the merge commit, the cover letter can contain
the resolution of all the merge conflicts in a way that can be
shared and reviewed.&lt;/p&gt;
&lt;p&gt;In Jujutsu, where conflicts are first class, the cover letter
commit can contain unresolved conflicts: you don’t have to clean
them up when creating the merge, you can leave that job until
later.&lt;/p&gt;
&lt;p&gt;If you can share a prototype of your merge commit, then it becomes
possible for your collaborators to review any merge conflicts and
how you resolved them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To distinguish a cover letter from a merge commit object, a cover
letter object has a “target” header which is a special kind of parent
header. A cover letter also has a normal parent commit header that
refers to earlier commits in the feature branch. The target is what
will become the first parent of the eventual merge commit.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#previous-branch" name="previous-branch"&gt;previous branch&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The other ingredient is to add a “previous branch” header, another
special kind of parent commit header. The previous branch header
refers to an older version of the cover letter and, transitively, an
older version of the whole feature branch.&lt;/p&gt;
&lt;p&gt;Typically the previous branch header will match the last shared
version of the branch, i.e. the commit hash of the server’s copy of
the feature branch.&lt;/p&gt;
&lt;p&gt;The previous branch header isn’t changed during normal work on the
feature branch. As the branch is revised and rebased, the commit hash
of the cover letter will change fairly frequently. These changes are
recorded in git’s reflog or jj’s oplog, but not in the “previous
branch” chain.&lt;/p&gt;
&lt;p&gt;You can use the previous branch chain to examine diffs between
versions of the feature branch as a whole. If commits have
Gerrit-style or jj-style change-IDs then it’s fairly easy to find and
compare previous versions of an individual commit.&lt;/p&gt;
&lt;p&gt;The previous branch header supports &lt;a href="https://gist.github.com/thoughtpolice/9c45287550a56b2047c6311fbadebed2"&gt;interdiff code
review&lt;/a&gt;,
or allows you to retain past iterations of a patch series.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#workflow" name="workflow"&gt;workflow&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are some sketchy notes on how these features might work in
practice.&lt;/p&gt;
&lt;p&gt;One way to use cover letters is jj-style, where it’s convenient to
edit commits that aren’t at the tip of a branch, and easy to reshuffle
commits so that a branch has a deliberate narrative.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When you create a new feature branch, it starts off as an empty
cover letter with both target and parent pointing at the same
commit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alternatively, you might start a branch ad hoc, and later cap it
with a cover letter.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If this is a small change and rebase + fast-forward is allowed,
you can edit the “cover letter” to contain the whole change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Otherwise, you can hack on the branch any which way. Shuffle the
commits that should be part of the merge request so that they
occur before the cover letter, and edit the cover letter to
summarize the preceding commits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you first push the branch, there’s (still) no need to give it
a name: the server can see that this is (probably) going to be a
new merge request because the top commit has a target branch and
its change-ID doesn’t match an existing merge request.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Also when you push, your client automatically creates a new
instance of your cover letter, adding a “previous branch” header
to indicate that the old version was shared. The commits on the
branch that were pushed are now immutable; rebases and edits
affect the new version of the branch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;During review there will typically be multiple iterations of the
branch to address feedback. The chain of previous branch headers
allows reviewers to see how commits were changed to address
feedback, interdiff style.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The branch can be merged when the target header matches the
current trunk and there are no conflicts left to resolve.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the time comes to merge the branch, there are several options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For a merge workflow, the cover letter is used to make a new
commit on the trunk, changing the target header into the first
parent commit, and dropping the previous branch header.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or, if you like to preserve more history, the previous branch
chain can be retained.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or you can drop the cover letter and fast foward the branch on to
the trunk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or you can squash the branch on to the trunk, using the cover
letter as the commit message.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-09-11-cover-letter.html#questions" name="questions"&gt;questions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is a fairly rough idea: I’m sure that some of the details won’t
work in practice without a lot of careful work on compatibility and
deployability.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Do the new commit headers (“target” and “previous branch”) need to
be headers?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What are the compatibility issues with adding new headers that
refer to other commits?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How would a server handle a push of an unnamed branch? How could
someone else pull a copy of it?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How feasible is it to use cover letter subject lines instead of
branch names?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The previous branch header is doing a similar job to a remote
tracking branch. Is there an opportunity to simplify how we keep a
local cache of the server state?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite all that, I think something along these lines could make
branches / reviews / reworks / merges less awkward. How you merge
should me a matter of your project’s preferred style, without
interference from technical limitations that force you to trade off
one annoyance against another.&lt;/p&gt;
&lt;p&gt;There remains a non-technical limitation: I have assumed that
contributors are comfortable enough with version control to use a
history-editing workflow effectively. I’ve lost all perspective on how
hard this is for a newbie to learn; I expect (or hope?) jj makes it
much easier than git rebase.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Thu, 11 Sep 2025 04:26:47 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-09-11-cover-letter.html</guid></item><item><title>The idiomatic ZFS on Linux quickstart cheat-sheet</title><link>https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/</link><description>&lt;p&gt;I&amp;#8217;m a FreeBSD guy that has had a long, serious, and very much monogamous relationship with ZFS. I experimented with Solaris 9 to learn about ZFS, adopted OpenSolaris (2008?) back in the &amp;#8220;aughts&amp;#8221; for my first ZFS server, transitioned my &amp;#8230; &lt;a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/"&gt;The idiomatic ZFS on Linux quickstart cheat-sheet&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Wed, 10 Sep 2025 23:10:18 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/</guid></item><item><title>A simple clustering and replication solution for Postgres</title><link>http://notes.eatonphil.com/2025-09-10-simple-clustering-and-replication-solution-postgres.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/simple-clustering-and-replication-solution-postgres"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 10 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-09-10-simple-clustering-and-replication-solution-postgres.html</guid></item><item><title>Cognition: The Devin is in the Details</title><link>https://www.swyx.io/cognition</link><description>&lt;p&gt;My thesis for the future of software dev agents. This is a hastily written blogpost done on not a lot of sleep, so pardon poor pacing and structure and typos and mistakes but just getting it out there.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 08 Sep 2025 18:26:44 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/cognition</guid></item><item><title>Ex-X</title><link>http://blog.jgc.org/2025/09/ex-x.html</link><description>&lt;p&gt;I did have an X.com (username: 6a6763) account and I got bored of it so I deactivated it today.&amp;nbsp;&lt;/p&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Sun, 07 Sep 2025 15:07:55 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/09/ex-x.html</guid></item><item><title>The CoPilot productivity paradox</title><link>https://www.marginalia.nu/log/a_125_ai_assistants/</link><description>&lt;p&gt;I&amp;rsquo;ve been using the CoPilot plugin for IntelliJ on and off for the last few years,
and while initially pretty enthusiastic,
I&amp;rsquo;ve come to first disable it and then delete it altogether along with JetBrains&amp;rsquo; local AI-completions,
and generally felt this has been an improvement in productivity and a reduction of frustration.&lt;/p&gt;
&lt;p&gt;CoPilot is pretty good at taking things that are already pretty fast,
such as monotonous code transformations like mapping an object to a SQL statement,
and then making that even faster.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 06 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_125_ai_assistants/</guid></item><item><title>A brief discussion of the Practical Byzantine Fault Tolerance (PBFT) algorithm</title><link>https://bytepawn.com/practical-byzantine-fault-tolerance.html</link><description>&lt;p&gt;This article explains how Practical Byzantine Fault Tolerance (PBFT) builds on Lamport’s theoretical consensus algorithms by introducing leaders, quorums, and cryptographic optimizations to make Byzantine consensus practical, contrasts it with Paxos and crash-fault tolerance, and explores its influence on modern blockchain protocols.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/pbft-phases.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 06 Sep 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/practical-byzantine-fault-tolerance.html</guid></item><item><title>LLMs are not really a horse or a model-t</title><link>https://jodavaho.io/posts/ai-analogies.html</link><description>&lt;p&gt;This horse/car analogy for LLMs is very weird.&lt;/p&gt;
&lt;p&gt;Who&amp;rsquo;s the engineer here?&lt;/p&gt;
&lt;p&gt;Are we sure this isn&amp;rsquo;t a CEO perspective where they are annoyed things can&amp;rsquo;t
magically go faster and why do we have to feed these dumb engineers?&lt;/p&gt;
&lt;p&gt;Have we considered that cars can&amp;rsquo;t break new ground and require roads to and
from the destination?&lt;/p&gt;
&lt;p&gt;LLMs have a kind of have a &amp;ldquo;mental GPS&amp;rdquo; functionality sometimes: You can always
check in with an LLM in &amp;ldquo;interactive encyclopedia mode&amp;rdquo; to get a vague sense of
where you are and what&amp;rsquo;s around you, but it can be off on the details quite a
bit.&lt;/p&gt;
&lt;p&gt;They are kind of like a self-driving car vs a real car: If I need to go
somewhere that millions of other people have gone and do so daily, I can get
there with zero thought now but I have to pay attention still.&lt;/p&gt;
&lt;p&gt;The closest analogy is CNC vs hand milling, honestly.&lt;/p&gt;
&lt;p&gt;The problem is that CNC has a ton of up-front design work with computer-aided
drafting to make sure the damn thing is fully sketched out to the millimeter.
We have no such tool for software. And no LLM is precise to the millimeter so
to speak.&lt;/p&gt;
&lt;p&gt;AI like we have now is something new. Far short of the promises, but
monumentally different than other tools we&amp;rsquo;ve built. It&amp;rsquo;s neat. And everyone I
know uses it for casual things, so it&amp;rsquo;s here to stay. It&amp;rsquo;s impact on
engineering will be permanent but possibly not as thorough as promised.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Fri, 05 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/ai-analogies.html</guid></item><item><title>Coloring matched portions with GNU grep, sed and awk</title><link>https://learnbyexample.github.io/coloring-matched-portions-grep-sed-awk/</link><description>&lt;p&gt;You might already know how to use the &lt;code&gt;--color&lt;/code&gt; option to highlight matched portions with &lt;code&gt;GNU grep&lt;/code&gt;. In this post, you'll see how to use ANSI escape sequences to format matched portions with &lt;code&gt;GNU sed&lt;/code&gt; and &lt;code&gt;GNU awk&lt;/code&gt;.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="gnu-grep"&gt;GNU grep&lt;a class="zola-anchor" href="#gnu-grep"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Consider this sample input file:&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat fruits.txt
&lt;/span&gt;&lt;span&gt;banana mango cherry pineapple
&lt;/span&gt;&lt;span&gt;grape fig apple dragonfruit papaya
&lt;/span&gt;&lt;span&gt;watermelon cashew tomato orange
&lt;/span&gt;&lt;span&gt;almond lime grapefruit walnut
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output for &lt;code&gt;grep --color -wE '[ago]\w+' fruits.txt&lt;/code&gt; is shown below:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Example for displaying matched portions in color with GNU grep" src="/images/grep_color.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/frequently-used-options.html#colored-output"&gt;this section&lt;/a&gt; from my ebook on &lt;code&gt;GNU grep&lt;/code&gt; for more details about this option. &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; has a more featured support for color formatting, see &lt;a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/ripgrep.html#colored-output"&gt;this section&lt;/a&gt; for an example.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="formatting-with-ansi-escape-sequences"&gt;Formatting with ANSI escape sequences&lt;a class="zola-anchor" href="#formatting-with-ansi-escape-sequences"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are some examples to show how you can format text in the terminal using ANSI escape sequences:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Examples for formatting with ANSI escape sequences" src="/images/ANSI_escape_sequences_formatting.png" /&gt;&lt;/p&gt;
&lt;p&gt;Your choice of formatting goes between &lt;code&gt;\033[&lt;/code&gt; and &lt;code&gt;m&lt;/code&gt;. You can use &lt;code&gt;01&lt;/code&gt; for &lt;strong&gt;bold&lt;/strong&gt;, &lt;code&gt;03&lt;/code&gt; for &lt;em&gt;italics&lt;/em&gt; and &lt;code&gt;04&lt;/code&gt; for &lt;u&gt;underline&lt;/u&gt;. &lt;code&gt;31&lt;/code&gt; is for the color &lt;font color="red"&gt;red&lt;/font&gt;, &lt;code&gt;32&lt;/code&gt; for &lt;font color="green"&gt;green&lt;/font&gt; and &lt;code&gt;34&lt;/code&gt; for &lt;font color="blue"&gt;blue&lt;/font&gt;. Multiple formats can be specified by separating the parameters with a semicolon. Using &lt;code&gt;0&lt;/code&gt; turns off the format (otherwise, it will persist in the current terminal session until turned off).&lt;/p&gt;
&lt;p&gt;If you find a file that has accidentally saved such escape sequences, you can use &lt;code&gt;cat -v&lt;/code&gt; to identify them.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ echo 'one (two) three' | grep --color=always '(two)' | cat -v
&lt;/span&gt;&lt;span&gt;one ^[[01;31m^[[K(two)^[[m^[[K three
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/q/4842424/4082052"&gt;List of ANSI color escape sequences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unix.stackexchange.com/q/148/109046"&gt;Colorizing your terminal and shell environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wizardzines.com/comics/colours/"&gt;A comic on terminal colors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Note that you can also use &lt;code&gt;\e&lt;/code&gt; instead of &lt;code&gt;\033&lt;/code&gt; in the above examples. However, that will not work with the &lt;code&gt;GNU awk&lt;/code&gt; examples shown below.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="gnu-sed"&gt;GNU sed&lt;a class="zola-anchor" href="#gnu-sed"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;GNU sed&lt;/code&gt;, you'll need to use &lt;code&gt;\o&lt;/code&gt; to specify an octal escape sequence. Here's an example: &lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Example for displaying matched portions in color with GNU sed" src="/images/sed_color.png" /&gt;&lt;/p&gt;
&lt;p&gt;Here's an example for processing lines bounded by distinct markers:&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat blocks.txt
&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;icecream
&lt;/span&gt;&lt;span&gt;--start 1--
&lt;/span&gt;&lt;span&gt;dragon 1234
&lt;/span&gt;&lt;span&gt;unicorn 6789
&lt;/span&gt;&lt;span&gt;**end 1**
&lt;/span&gt;&lt;span&gt;have a nice day
&lt;/span&gt;&lt;span&gt;--start 2--
&lt;/span&gt;&lt;span&gt;a b c
&lt;/span&gt;&lt;span&gt;apple banana cherry
&lt;/span&gt;&lt;span&gt;**end 2**
&lt;/span&gt;&lt;span&gt;par,far,mar,tar
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p align="center"&gt;&lt;img alt="Highlighting portion of interest in multiline processing with GNU sed" src="/images/sed_multiline_color.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="gnu-awk"&gt;GNU awk&lt;a class="zola-anchor" href="#gnu-awk"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;GNU awk&lt;/code&gt;, you can embed the ANSI escape sequences in a string similar to the &lt;code&gt;printf&lt;/code&gt; example seen earlier.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Example for displaying matched portions in color with GNU awk" src="/images/awk_color.png" /&gt;&lt;/p&gt;
&lt;p&gt;Here's a field processing example:&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat marks.txt 
&lt;/span&gt;&lt;span&gt;Dept    Name    Marks
&lt;/span&gt;&lt;span&gt;ECE     Raj     53
&lt;/span&gt;&lt;span&gt;ECE     Joel    62
&lt;/span&gt;&lt;span&gt;EEE     Moi     68
&lt;/span&gt;&lt;span&gt;CSE     Surya   81
&lt;/span&gt;&lt;span&gt;EEE     Tia     59
&lt;/span&gt;&lt;span&gt;ECE     Om      92
&lt;/span&gt;&lt;span&gt;CSE     Amy     67
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat filter.txt 
&lt;/span&gt;&lt;span&gt;ECE 70
&lt;/span&gt;&lt;span&gt;EEE 65
&lt;/span&gt;&lt;span&gt;CSE 80
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p align="center"&gt;&lt;img alt="Color field processing results with GNU awk" src="/images/awk_color_field_processing.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="linux-cli-ebooks"&gt;Linux CLI ebooks&lt;a class="zola-anchor" href="#linux-cli-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href="https://learnbyexample.github.io/books/"&gt;my ebooks&lt;/a&gt; if you are interested in learning more about Linux CLI basics, coreutils, text processing tools like &lt;code&gt;GNU grep&lt;/code&gt;, &lt;code&gt;GNU sed&lt;/code&gt;, &lt;code&gt;GNU awk&lt;/code&gt;, &lt;code&gt;perl&lt;/code&gt; and more! You can get them all as a single bundle via &lt;a href="https://leanpub.com/b/learnbyexample-all-books"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books"&gt;gumroad&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 05 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/coloring-matched-portions-grep-sed-awk/</guid></item><item><title>18XX: A System of Systems</title><link>https://blog.fogus.me/games/18XX/intro.html</link><description>Whereby I talk about 18XX -- the fantastic family of economic tabletop games...</description><author>Send More Paramedics</author><pubDate>Thu, 04 Sep 2025 11:44:49 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/games/18XX/intro.html</guid></item><item><title>Analytics query goes 6x faster with EDB Postgres Distributed's new analytics engine</title><link>http://notes.eatonphil.com/2025-09-04-analytics-query-goes-6x-faster-edb-postgres-distributeds-new-analytics-engine.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/analytics-query-goes-6x-faster-edb-postgres-distributeds-new-analytics-engine"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 04 Sep 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-09-04-analytics-query-goes-6x-faster-edb-postgres-distributeds-new-analytics-engine.html</guid></item><item><title>Addendum B: Client Entitlement Setup</title><link>https://littlegreenviper.com/addendum-b-client-entitlement-setup/</link><description>Once the server is set up, we need to get entitlements working on the client end. What&amp;#8217;s the Deal With &amp;#8220;Entitlements&amp;#8221;? Entitlements are Apple&amp;#8217;s name for settings that are &amp;#8220;baked into&amp;#8221; the app, and signal the hardware to &amp;#8220;open certain doors&amp;#8221; for the software. Apple requires entitlements to be present, when accessing various technology SDKs ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/addendum-b-client-entitlement-setup/#more-7632" rel="noopener noreferrer" title="Addendum B: Client Entitlement Setup"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Wed, 03 Sep 2025 20:07:16 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/addendum-b-client-entitlement-setup/</guid></item><item><title>Addendum A: Server Setup</title><link>https://littlegreenviper.com/addendum-a-server-setup/</link><description>In this posting, we&amp;#8217;ll discuss the server setup for the demo app. The passkey is used in two places: Registration (creating an account), and logging in. After login, the server works in the same way that most of these types of things work. Server Configuration NOTE: The server source files are located in this directory. ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/addendum-a-server-setup/#more-7618" rel="noopener noreferrer" title="Addendum A: Server Setup"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Tue, 02 Sep 2025 19:32:32 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/addendum-a-server-setup/</guid></item><item><title>Baby's first type checker</title><link>https://austinhenley.com/blog/babytypechecker.html</link><description>&lt;a href="https://austinhenley.com/blog/babytypechecker.html"&gt;https://austinhenley.com/blog/babytypechecker.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 31 Aug 2025 21:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/babytypechecker.html</guid></item><item><title>Lamport's Byzantine Consensus algorithm with Signatures</title><link>https://bytepawn.com/byzantine-consensus-with-signatures.html</link><description>&lt;p&gt;This article, following Lamport's work, extends the original Byzantine Generals algorithm from “oral-messages” to signed messages, showing how digital signatures drastically simplify the problem and reduce message complexity, with an implementation given in Python using Flask HTTP nodes.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-byzantine-generals-signed.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 31 Aug 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/byzantine-consensus-with-signatures.html</guid></item><item><title>Loss as the Price of Love</title><link>https://andyjohnson.uk/blog/2025/08/29/loss-as-the-price-of-love/</link><description>It is perhaps a truism that to love is to acknowledge the inevitability of loss. Either by time or circumstances, all that you love will be lost. People will change, depart, die, eventually become dust. Nothing lasts, and we will lose all that we love. But still we love anyway. This has been on my &amp;#8230; &lt;a class="more-link" href="https://andyjohnson.uk/blog/2025/08/29/loss-as-the-price-of-love/"&gt;Continue reading&lt;span class="screen-reader-text"&gt; "Loss as the Price of Love"&lt;/span&gt;&lt;/a&gt;</description><author>Digital Apocrypha</author><pubDate>Fri, 29 Aug 2025 23:21:21 GMT</pubDate><guid isPermaLink="true">https://andyjohnson.uk/blog/2025/08/29/loss-as-the-price-of-love/</guid></item><item><title>Login Passkey Code Walkthrough</title><link>https://littlegreenviper.com/passkey-login-code-walkthrough/</link><description>Logging in is simpler, but we still have the challenge/response process, as we did with creation. With logging in, the client doesn&amp;#8217;t need the server to prepare to create a new account, so it just needs a challenge. What to Look At The login process calls a number of methods within the client and server. ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/passkey-login-code-walkthrough/#more-7541" rel="noopener noreferrer" title="Login Passkey Code Walkthrough"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Fri, 29 Aug 2025 22:21:48 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/passkey-login-code-walkthrough/</guid></item><item><title>The Process of Login</title><link>https://littlegreenviper.com/the-process-of-login/</link><description>Logging into a server, using an existing passkey, is a lot simpler than creating a new account and passkey. Prerequisite You can only login, using a passkey, if one has already been established, through the creation process. It is assumed that the server has properly stored at least the Public Key, and the Credential ID ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/the-process-of-login/#more-7508" rel="noopener noreferrer" title="The Process of Login"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Fri, 29 Aug 2025 16:38:17 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/the-process-of-login/</guid></item><item><title>The Jujutsu Effect</title><link>https://blog.nawaz.org/posts/2025/Aug/the-jujutsu-effect/</link><description>&lt;p&gt;I began using &lt;a class="reference external" href="https://github.com/jj-vcs/jj"&gt;jujutsu&lt;/a&gt; only recently.
One month in, I was really liking it, but couldn&amp;#8217;t find a compelling
reason to recommend it. Overall, it wasn&amp;#8217;t a transformative experience -
just a nicer&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;Or so I&amp;nbsp;thought.&lt;/p&gt;
&lt;p&gt;Then I switched to a project that needed
&lt;a class="reference external" href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt; to work …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Fri, 29 Aug 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Aug/the-jujutsu-effect/</guid></item><item><title>Create Passkey Code Walkthrough</title><link>https://littlegreenviper.com/create-passkey-code-walkthrough/</link><description>In our code walkthrough, we&amp;#8217;ll look at both the client code (Swift), and the server code (PHP). The creation process is the most code-intensive part of using passkeys. What to Look At In this post, we&amp;#8217;ll be looking at some particular methods, within each of the implementation classes. These are: The Client The Server Steps ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/create-passkey-code-walkthrough/#more-7416" rel="noopener noreferrer" title="Create Passkey Code Walkthrough"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Thu, 28 Aug 2025 15:14:00 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/create-passkey-code-walkthrough/</guid></item><item><title>strongly typed?</title><link>https://dotat.at/@/2025-08-28-strongly-typed.html</link><description>&lt;p&gt;What does it mean when someone writes that a programming language is
“strongly typed”?&lt;/p&gt;
&lt;p&gt;I’ve known for many years that “strongly typed” is a poorly-defined
term. Recently I was prompted on &lt;a href="https://lobste.rs"&gt;Lobsters&lt;/a&gt; to
explain why it’s hard to understand what someone means when they use
the phrase.&lt;/p&gt;
&lt;p&gt;I came up with more than five meanings!&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-28-strongly-typed.html#how-strong-" name="how-strong-"&gt;how strong?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The various meanings of “strongly typed” are not clearly yes-or-no.
Some developers like to argue that these kinds of integrity checks
must be completely perfect or else they are entirely worthless.
Charitably (it took me a while to think of a polite way to phrase
this), that betrays a lack of engineering maturity.&lt;/p&gt;
&lt;p&gt;Software engineers, like any engineers, have to create working systems
from imperfect materials. To do so, we must understand what guarantees
we can rely on, where our mistakes can be caught early, where we need
to establish processes to catch mistakes, how we can control the
consequences of our mistakes, and how to remediate when somethng
breaks because of a mistake that wasn’t caught.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-28-strongly-typed.html#strong-how-" name="strong-how-"&gt;strong how?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So, what are the ways that a programming language can be strongly or
weakly typed? In what ways are real programming languages “mid”?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Statically typed as opposed to dynamically typed?&lt;/p&gt;
&lt;p&gt;Many languages have a mixture of the two, such as run time
polymorphism in OO languages (e.g. Java), or gradual type systems
for dynamic languages (e.g. TypeScript).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sound static type system?&lt;/p&gt;
&lt;p&gt;It’s common for static type systems to be deliberately unsound,
such as covariant subtyping in arrays or functions (Java, again).
Gradual type systems migh have gaping holes for usability reasons
(TypeScript, again).&lt;/p&gt;
&lt;p&gt;And some type systems might be unsound due to bugs. (There are a
few of these in Rust.)&lt;/p&gt;
&lt;p&gt;Unsoundness isn’t a disaster, if a programmer won’t cause it
without being aware of the risk.&lt;/p&gt;
&lt;p&gt;For example: in Lean you can write “sorry” as a kind of “to do”
annotation that deliberately breaks soundness; and Idris 2 has
type-in-type so it accepts Girard’s paradox.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type safe at run time?&lt;/p&gt;
&lt;p&gt;Most languages have facilities for deliberately bypassing type
safety, with an “unsafe” library module or “unsafe” language
features, or things that are harder to spot. It can be more or
less difficult to break type safety in ways that the programmer or
language designer did not intend.&lt;/p&gt;
&lt;p&gt;JavaScript and Lua are very safe, treating type safety failures as
security vulnerabilities. Java and Rust have controlled unsafety.
In C everything is unsafe.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fewer weird implicit coercions?&lt;/p&gt;
&lt;p&gt;There isn’t a total order here: for instance, C has implicit
bool/int coercions, Rust does not; Rust has implicit deref, C does
not.&lt;/p&gt;
&lt;p&gt;There’s a huge range in how much coercions are a convenience or a
source of bugs. For example, the PHP and JavaScript == operators
are made entirely of WAT, but at least you can use === instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How fancy is the type system?&lt;/p&gt;
&lt;p&gt;To what degree can you model properties of your program as types?
Is it convenient to parse, not validate? Is the Curry-Howard
correspondance something you can put into practice? Or is it only
capable of describing the physical layout of data?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are probably other meanings, e.g. I have seen “strongly typed”
used to mean that runtime representations are abstract (you can’t see
the underlying bytes); or in the past it sometimes meant a language
with a heavy type annotation burden (as a mischaracterization of
static type checking).&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-28-strongly-typed.html#how-to-type" name="how-to-type"&gt;how to type&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So, when you write (with your keyboard) the phrase “strongly typed”,
delete it, and come up with a more precise description of what you
really mean. The desiderata above are partly overlapping, sometimes
partly orthogonal. Some of them you might care about, some of them
not.&lt;/p&gt;
&lt;p&gt;But please try to communicate where you draw the line and how fuzzy
your line is.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Thu, 28 Aug 2025 04:33:05 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-08-28-strongly-typed.html</guid></item><item><title>Snark, Ironic Detachment, Authenticity</title><link>https://www.marginalia.nu/log/a_124_snark_and_insincerity/</link><description>&lt;p&gt;How you engage with the world changes how you experience
the world, and how the world experiences you.&lt;/p&gt;
&lt;p&gt;A snarky and cynical approach, by its default assumption that
things are shit, or if they are not yet shit will inevitably turn to;
such an approach will give your world a malodorous brownish tint.&lt;/p&gt;
&lt;p&gt;Granted, snark gives you plausible deniability,
a motte-and-bailey that protects you from direct criticism,
encountering backlash you can always backpedal and say it
was just a joke that you accidentally took a bit too far.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 28 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_124_snark_and_insincerity/</guid></item><item><title>searchcode.com is being rebooted (maybe?)</title><link>https://boyter.org/posts/searchcode-is-being-rebooted/</link><description>&lt;p&gt;&lt;strong&gt;searchcode HAS been rebooted&lt;/strong&gt; see &lt;a href="https://boyter.org/posts/searchcode-has-been-rebooted/"&gt;https://boyter.org/posts/searchcode-has-been-rebooted/&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;By the time you are reading this, searchcode.com&amp;rsquo;s DNS should have been updated to point at a placeholder index.html file I created.&lt;/p&gt;
&lt;p&gt;In short I am shutting down the current iteration of searchcode.com, unless something drastic happens in the next few days.&lt;/p&gt;
&lt;p&gt;The reasons for this&amp;hellip; are few, but worth exploring.&lt;/p&gt;
&lt;p&gt;While I have often described searchcode as a fun forever project, big enough to be interesting, but small enough to be done by one person, I probably should have clarified that this was true so long as one or two of the following things held true,&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 28 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/searchcode-is-being-rebooted/</guid></item><item><title>The Process of Creation</title><link>https://littlegreenviper.com/the-process-of-creation/</link><description>In order to use a passkey, the client needs to create the key, and coordinate with the server. We should note that this is a VERY BASIC EXAMPLE. It&amp;#8217;s quite possible to get a lot more involved, and a lot more secure. Multi-Step Process There are a few steps that we take, in creating an ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/the-process-of-creation/#more-7398" rel="noopener noreferrer" title="The Process of Creation"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Wed, 27 Aug 2025 22:47:13 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/the-process-of-creation/</guid></item><item><title>Connect Four game with a twist</title><link>https://learnbyexample.github.io/connect-four-game-with-a-twist/</link><description>&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Connect Square game" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/ConnectSquare/connect_square.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;p&gt;From &lt;a href="https://en.wikipedia.org/wiki/Connect_Four"&gt;wikipedia: Connect Four&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Connect Four is a game in which the players choose a color and then take turns dropping colored tokens into a six-row, seven-column vertically suspended grid. The pieces fall straight down, occupying the lowest available space within the column. The objective of the game is to be the first to form a horizontal, vertical, or diagonal line of four of one's own tokens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a twist, this TUI implementation also offers two more variations of the game:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;form a square, i.e. four cells forming 90 degree angles and equidistant from each other&lt;/li&gt;
&lt;li&gt;form a line or square&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This app is available on PyPI as &lt;a href="https://pypi.org/project/connectsquare/"&gt;connectsquare&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install connectsquare
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; connectsquare
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add this alias to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;connectsquare&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/connectsquare'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative to manually managing such virtual environments, you can use &lt;a href="https://github.com/astral-sh/uv"&gt;uv&lt;/a&gt; or &lt;a href="https://github.com/pypa/pipx"&gt;pipx&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;As yet another alternative, you can install &lt;code&gt;textual==0.85.2&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone &lt;a href="https://github.com/learnbyexample/TUI-apps"&gt;this repository&lt;/a&gt; and run the &lt;code&gt;connect_square.py&lt;/code&gt; file.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="screenshots"&gt;Screenshots&lt;a class="zola-anchor" href="#screenshots"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adjust your terminal's dimension for the game widgets to appear properly, for example 80x30 (characters x lines). Sample screenshots are shown below:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Connect Four game" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/ConnectSquare/connect_four.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Connect Four or Square game" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/ConnectSquare/connect_both.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="guide"&gt;Guide&lt;a class="zola-anchor" href="#guide"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Press the &lt;strong&gt;n&lt;/strong&gt; key to start a new game. Existing game, if any, will be abandoned&lt;/li&gt;
&lt;li&gt;You can choose between &lt;strong&gt;Connect Four&lt;/strong&gt;, &lt;strong&gt;Connect Square&lt;/strong&gt; (default) and &lt;strong&gt;Both&lt;/strong&gt; types of game&lt;/li&gt;
&lt;li&gt;You can choose between &lt;strong&gt;Easy&lt;/strong&gt; (default), &lt;strong&gt;Medium&lt;/strong&gt; and &lt;strong&gt;Hard&lt;/strong&gt; difficulty modes:
&lt;ul&gt;
&lt;li&gt;In the &lt;em&gt;Easy&lt;/em&gt; mode, the AI will make a random move&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;Medium&lt;/em&gt; mode, the AI will make a random move based on certain weight calculations&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;Hard&lt;/em&gt; mode, the AI will make the best move based on the weight calculations (the algorithm is based only on the current board state and thus it is not impossible for the user to win)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The first move is based on the &lt;strong&gt;User first&lt;/strong&gt; (default) and &lt;strong&gt;AI first&lt;/strong&gt; choices&lt;/li&gt;
&lt;li&gt;Only the bottom most empty cell of each column will be considered as a valid move&lt;/li&gt;
&lt;li&gt;Press the &lt;strong&gt;t&lt;/strong&gt; key to toggle between light and dark themes&lt;/li&gt;
&lt;li&gt;Press the &lt;strong&gt;q&lt;/strong&gt; key to quit the app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;User moves are denoted by the ⭕️ character and AI moves are denoted by the ✖️  character.&lt;/p&gt;
&lt;p&gt;The text panel under the game board displays the current status of the game. If the game ends with one of the players forming a valid line or square, the cells forming the winning move will be highlighted.&lt;/p&gt;
&lt;h2 id="square-tic-tac-toe"&gt;Square tic tac toe&lt;a class="zola-anchor" href="#square-tic-tac-toe"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you liked this game, you might also enjoy &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/SquareTicTacToe"&gt;Square tic tac toe&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are interested in learning more about the AI algorithm for the Connect Square game, check out my &lt;a href="https://learnbyexample.github.io/practice_python_projects/square_tic_tac_toe/square_tic_tac_toe_ai.html"&gt;explanation here for Square tic tac toe&lt;/a&gt; — while there are a few differences between the two, the foundation is the same.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 26 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/connect-four-game-with-a-twist/</guid></item><item><title>A Closer Look At Passkeys</title><link>https://littlegreenviper.com/a-closer-look-at-passkeys/</link><description>This series will focus on demonstrating a simple client (iOS) and server (PHP/MySQL) implementation of the most basic type of passkey. This app has already been written, so we&amp;#8217;ll look at it, and walk through its operation, in order to learn about implementing passkeys in an iOS app. Our guide through this journey, will be ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/a-closer-look-at-passkeys/#more-7307" rel="noopener noreferrer" title="A Closer Look At Passkeys"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Tue, 26 Aug 2025 00:21:19 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/a-closer-look-at-passkeys/</guid></item><item><title>Stop Polluting Context - Let Users Disable Individual MCP Tools</title><link>https://smcleod.net/2025/08/stop-polluting-context-let-users-disable-individual-mcp-tools/</link><description>If you&amp;rsquo;re building MCP servers, you should be adding the ability to disable individual tools.</description><author>smcleod.net</author><pubDate>Mon, 25 Aug 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/08/stop-polluting-context-let-users-disable-individual-mcp-tools/</guid></item><item><title>NixOS PostgreSQL Major Version Upgrade</title><link>https://kevincox.ca/2025/08/24/nixos-postgres-upgrade/</link><description>&lt;p style="margin: 0; padding: 0;"&gt;PostgreSQL major version upgrades is something that NixOS currently doesn’t handle for you. This is my process, documented mostly so that I can remember it next time I want to upgrade.&lt;/p&gt;&lt;aside class="_1 _2"&gt;&lt;b style="margin: 0; padding: 0;"&gt;Warning&lt;/b&gt;&lt;p style="margin: 0; padding: 0;"&gt;I highly recommend that you read the &lt;a class="_3" href="https://www.postgresql.org/docs/17/upgrading.html"&gt;PostgreSQL Upgrading documentation&lt;/a&gt;. It is well written and gives important caveats. It would be risky to follow through these steps without understanding the process.&lt;/p&gt;&lt;/aside&gt;&lt;h2 class="_4" id="configuration" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/08/24/nixos-postgres-upgrade/#configuration" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Configuration&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;I use the standard NixOS module for Postgres. My main config looks like this:&lt;/p&gt;&lt;pre class="_5"&gt;&lt;code class="_6 _7 language-nix"&gt;{ &lt;span class="_8"&gt;lib&lt;/span&gt;&lt;span class="_9"&gt;,&lt;/span&gt; &lt;span class="_8"&gt;pkgs&lt;/span&gt;&lt;span class="_9"&gt;,&lt;/span&gt; &lt;span class="_9"&gt;... &lt;/span&gt;}: {
	&lt;span class="_a"&gt;services&lt;/span&gt;.&lt;span class="_a"&gt;postgresql&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; {
		&lt;span class="_a"&gt;enable&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; &lt;span class="_b"&gt;true&lt;/span&gt;;
		&lt;span class="_a"&gt;package&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; &lt;span class="_c"&gt;pkgs&lt;/span&gt;&lt;span class="_9"&gt;.&lt;/span&gt;&lt;span class="_c"&gt;postgresql_17&lt;/span&gt;;
		&lt;span class="_a"&gt;initdbArgs&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; [
			&lt;span class="_d"&gt;&lt;span class="_d"&gt;"&lt;/span&gt;--encoding=UTF8&lt;span class="_d"&gt;"&lt;/span&gt;&lt;/span&gt;
			&lt;span class="_d"&gt;&lt;span class="_d"&gt;"&lt;/span&gt;--no-locale&lt;span class="_d"&gt;"&lt;/span&gt;&lt;/span&gt;
		];
		&lt;span class="_a"&gt;ensureUsers&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; [{
			&lt;span class="_a"&gt;name&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; &lt;span class="_d"&gt;&lt;span class="_d"&gt;"&lt;/span&gt;kevincox&lt;span class="_d"&gt;"&lt;/span&gt;&lt;/span&gt;;
		} {
			&lt;span class="_a"&gt;name&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; &lt;span class="_d"&gt;&lt;span class="_d"&gt;"&lt;/span&gt;root&lt;span class="_d"&gt;"&lt;/span&gt;&lt;/span&gt;;
		}];
	};

	&lt;span class="_a"&gt;systemd&lt;/span&gt;.&lt;span class="_a"&gt;services&lt;/span&gt;.&lt;span class="_a"&gt;postgresql&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; {
		&lt;span class="_a"&gt;postStart&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; &lt;span class="_c"&gt;lib&lt;/span&gt;&lt;span class="_9"&gt;.&lt;/span&gt;&lt;span class="_c"&gt;mkAfter&lt;/span&gt; &lt;span class="_d"&gt;&lt;span class="_d"&gt;''&lt;/span&gt;&lt;/span&gt;
&lt;span class="_d"&gt;			$PSQL -tAc "ALTER ROLE kevincox with SUPERUSER"&lt;/span&gt;
&lt;span class="_d"&gt;			$PSQL -tAc "ALTER ROLE root with SUPERUSER"&lt;/span&gt;
&lt;span class="_d"&gt;		&lt;span class="_d"&gt;''&lt;/span&gt;&lt;/span&gt;;
		&lt;span class="_a"&gt;serviceConfig&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; {
			&lt;span class="_a"&gt;TimeoutStartSec&lt;/span&gt; &lt;span class="_9"&gt;=&lt;/span&gt; &lt;span class="_d"&gt;&lt;span class="_d"&gt;"&lt;/span&gt;30min&lt;span class="_d"&gt;"&lt;/span&gt;&lt;/span&gt;;
		};
	};
}
&lt;/code&gt;&lt;/pre&gt;&lt;p style="margin: 0; padding: 0;"&gt;Notable features:&lt;/p&gt;&lt;ol class="_e _f" style="margin: 0; padding: 0; padding-inline-start: 2em;"&gt;&lt;li style="margin: 0; padding: 0;"&gt;I pin the version of Postgres, to ensure that I upgrade when I want to.&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;I give myself and root superuser.&lt;/li&gt;&lt;/ol&gt;&lt;h2 class="_4" id="preparation" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/08/24/nixos-postgres-upgrade/#preparation" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Preparation&lt;/a&gt;&lt;/h2&gt;&lt;ol class="_e _f" style="margin: 0; padding: 0; padding-inline-start: 2em;"&gt;&lt;li style="margin: 0; padding: 0;"&gt;Update your NixOS config but &lt;strong style="margin: 0; padding: 0;"&gt;don’t&lt;/strong&gt; deploy it.&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;Build the config to make sure everything is ready to go.&lt;/li&gt;&lt;/ol&gt;&lt;h2 class="_4" id="upgrading" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/08/24/nixos-postgres-upgrade/#upgrading" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Upgrading&lt;/a&gt;&lt;/h2&gt;&lt;ol class="_e _f" style="margin: 0; padding: 0; padding-inline-start: 2em;"&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Log in as the &lt;code class="_g language-zsh"&gt;postgres&lt;/code&gt; user. (&lt;code class="_g language-zsh"&gt;sudo -su postgres&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Define the old version &lt;code class="_g language-zsh"&gt;old=16&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Get the old and new versions:&lt;/p&gt;&lt;pre class="_5"&gt;&lt;code class="_g language-sh"&gt;&lt;span class="_b"&gt;cd&lt;/span&gt; /var/lib/postgresql/
pg_old=&lt;span class="_d"&gt;&lt;span class="_d"&gt;$(&lt;/span&gt;nix-build --no-out-link -A postgresql_&lt;span class="_8"&gt;${old&lt;/span&gt;&lt;span class="_9"&gt;:?&lt;/span&gt;&lt;span class="_8"&gt;}&lt;/span&gt; &lt;span class="_d"&gt;'&lt;/span&gt;&amp;lt;nixpkgs&amp;gt;&lt;span class="_d"&gt;')&lt;/span&gt;&lt;/span&gt;
pg_new=&lt;span class="_d"&gt;&lt;span class="_d"&gt;$(&lt;/span&gt;nix-build --no-out-link -A postgresql_&lt;span class="_d"&gt;$((&lt;/span&gt;old&lt;span class="_9"&gt;+&lt;/span&gt;&lt;span class="_b"&gt;1&lt;/span&gt;&lt;span class="_d"&gt;))&lt;/span&gt; &lt;span class="_d"&gt;'&lt;/span&gt;&amp;lt;nixpkgs&amp;gt;&lt;span class="_d"&gt;')&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;aside class="_h"&gt;&lt;b style="margin: 0; padding: 0;"&gt;Note&lt;/b&gt;&lt;p style="margin: 0; padding: 0;"&gt;If you are using extensions you will need them in &lt;code class="_g language-zsh"&gt;&lt;span class="_8"&gt;$pg_old&lt;/span&gt;&lt;/code&gt; and &lt;code class="_g language-zsh"&gt;&lt;span class="_8"&gt;$pg_new&lt;/span&gt;&lt;/code&gt;. If you are using the NixOS module you can make a package with &lt;code class="_g language-zsh"&gt;nix-build &lt;span class="_d"&gt;&lt;span class="_d"&gt;"&lt;/span&gt;&amp;lt;nixpkgs/nixos&amp;gt;&lt;span class="_d"&gt;"&lt;/span&gt;&lt;/span&gt; -A config.services.postgresql.finalPackage&lt;/code&gt;. If you are using &lt;a class="_3" href="https://colmena.cli.rs/"&gt;colmena&lt;/a&gt; you can do &lt;code class="_g language-zsh"&gt;colmena &lt;span class="_b"&gt;eval&lt;/span&gt; -E &lt;span class="_d"&gt;&lt;span class="_d"&gt;"&lt;/span&gt;{nodes, ...}: nodes.mynode.config.services.postgresql.finalPackage&lt;span class="_d"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/aside&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Initialize the new data directory. Be sure to use the arguments from your config!&lt;/p&gt;&lt;pre class="_5"&gt;&lt;code class="_g language-sh"&gt;&lt;span class="_8"&gt;$pg_new&lt;/span&gt;/bin/initdb --encoding=UTF8 --no-locale &lt;span class="_d"&gt;&lt;span class="_d"&gt;$((&lt;/span&gt;old&lt;span class="_9"&gt;+&lt;/span&gt;&lt;span class="_b"&gt;1&lt;/span&gt;&lt;span class="_d"&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Copy the configuration (this is really only needed if you have extensions that need to be loaded, the main value we care about is &lt;code class="_g language-zsh"&gt;shared_preload_libraries&lt;/code&gt;. But copying the whole file is easiest and the NixOS module will overwrite it anyways when starting the new version.).&lt;/p&gt;&lt;pre class="_5"&gt;&lt;code class="_g language-sh"&gt;cp -v {&lt;span class="_8"&gt;${old&lt;/span&gt;&lt;span class="_9"&gt;:?&lt;/span&gt;&lt;span class="_8"&gt;}&lt;/span&gt;,&lt;span class="_d"&gt;&lt;span class="_d"&gt;$((&lt;/span&gt;old&lt;span class="_9"&gt;+&lt;/span&gt;&lt;span class="_b"&gt;1&lt;/span&gt;&lt;span class="_d"&gt;))&lt;/span&gt;&lt;/span&gt;}/postgresql.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Run the check&lt;/p&gt;&lt;pre class="_5"&gt;&lt;code class="_g language-sh"&gt;&lt;span class="_8"&gt;$pg_new&lt;/span&gt;/bin/pg_upgrade \
	--old-bindir=&lt;span class="_8"&gt;$pg_old&lt;/span&gt;/bin \
	--new-bindir=&lt;span class="_8"&gt;$pg_new&lt;/span&gt;/bin \
	--old-datadir=/var/lib/postgresql/&lt;span class="_8"&gt;${old&lt;/span&gt;&lt;span class="_9"&gt;:?&lt;/span&gt;&lt;span class="_8"&gt;}&lt;/span&gt; \
	--new-datadir=/var/lib/postgresql/&lt;span class="_d"&gt;&lt;span class="_d"&gt;$((&lt;/span&gt;old&lt;span class="_9"&gt;+&lt;/span&gt;&lt;span class="_b"&gt;1&lt;/span&gt;&lt;span class="_d"&gt;))&lt;/span&gt;&lt;/span&gt; \
	-j16 \
	--clone \
	--check
&lt;/code&gt;&lt;/pre&gt;&lt;aside class="_h"&gt;&lt;b style="margin: 0; padding: 0;"&gt;Note&lt;/b&gt;&lt;p style="margin: 0; padding: 0;"&gt;If &lt;code class="_g language-zsh"&gt;--clone&lt;/code&gt; doesn’t work on your system you can try &lt;code class="_g language-zsh"&gt;--copy-file-range&lt;/code&gt; which should basically always work or if not just skip the argument altogether.&lt;/p&gt;&lt;/aside&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Stop the old Postgres. &lt;code class="_g language-zsh"&gt;systemctl stop postgresql&lt;/code&gt;&lt;/p&gt;&lt;aside class="_1 _2"&gt;&lt;b style="margin: 0; padding: 0;"&gt;Warning&lt;/b&gt;&lt;p style="margin: 0; padding: 0;"&gt;This is the point where we copy the data. If the old Postgres starts running again it will write new data to the old directory that will not be migrated. This will result in data loss. If that happens remove the new state directory and start again.&lt;/p&gt;&lt;/aside&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Run the migration. This is the same command as the check without &lt;code class="_g language-zsh"&gt;--check&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;Start the new PostgreSQL server. (This is typically done by activating the new configuration such as &lt;code class="_g language-zsh"&gt;sudo nixos-rebuild switch&lt;/code&gt;.)&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p style="margin: 0; padding: 0;"&gt;At this point the new version is running. Note that rolling back will cause data loss as new changes are being made only to the new data directory.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;You may wish to rename the old directory so that if you do accidentally roll back it is more likely that things fail rather than losing data. But if you have insecure apps that let the first user be admin that may be a bad idea. Maybe you could do something like put a file there or a directory owned by root? But I haven’t tested any of these things.&lt;/p&gt;&lt;h2 class="_4" id="cleanup" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/08/24/nixos-postgres-upgrade/#cleanup" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Cleanup&lt;/a&gt;&lt;/h2&gt;&lt;ol class="_e _f" style="margin: 0; padding: 0; padding-inline-start: 2em;"&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;As suggested by &lt;code class="_g language-zsh"&gt;pg_migrate&lt;/code&gt; run &lt;code class="_g language-zsh"&gt;vacuumdb --all --analyze-in-stages&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li style="margin: 0; padding: 0;"&gt;&lt;p style="margin: 0; padding: 0;"&gt;At some point in the future once you are confident that the new DB is running fine (a few weeks doesn’t hurt) you can remove the old data.&lt;/p&gt;&lt;pre class="_5"&gt;&lt;code class="_g language-sh"&gt;&lt;span class="_b"&gt;cd&lt;/span&gt; /var/lib/postgresql/
./delete_old_cluster.sh
rm -v delete_old_cluster.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;</description><author>Kevin Cox's Blog</author><pubDate>Sun, 24 Aug 2025 17:55:00 GMT</pubDate><guid isPermaLink="true">https://kevincox.ca/2025/08/24/nixos-postgres-upgrade/</guid></item><item><title>Applications of Byzantine Consensus</title><link>https://bytepawn.com/byzantine-consensus-applications.html</link><description>&lt;p&gt;This article examines where Lamport's original oral-messages Byzantine Generals algorithm does and does not apply in practice, contrasting unsuitable cases like distributed databases and rocket launches with a suitable case in redundant flight control systems.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/space-shuttle-launch.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 23 Aug 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/byzantine-consensus-applications.html</guid></item><item><title>Why Passkeys?</title><link>https://littlegreenviper.com/a-simple-demonstration-of-implementing-passkeys-in-ios/</link><description>Security has always been a difficult balance between usability and safety. Passkeys are a way to &amp;#8220;bridge&amp;#8221; these seemingly opposed concepts. They allow enhanced security between a server and a client, while also removing the need for the end-user to manage login details. The connection is more secure than that provided by the classic &amp;#8220;User ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/a-simple-demonstration-of-implementing-passkeys-in-ios/#more-7265" rel="noopener noreferrer" title="Why Passkeys?"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Thu, 21 Aug 2025 17:45:31 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/a-simple-demonstration-of-implementing-passkeys-in-ios/</guid></item><item><title>The long season of langdev</title><link>https://blog.fogus.me/langdev/long-season/</link><description>Whereby I talk about programming language development and why, and where it might go...</description><author>Send More Paramedics</author><pubDate>Tue, 19 Aug 2025 15:44:49 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/langdev/long-season/</guid></item><item><title>MCP DevTools</title><link>https://smcleod.net/2025/08/mcp-devtools/</link><description>A single, modular MCP server for AI coding agents.</description><author>smcleod.net</author><pubDate>Sun, 17 Aug 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/08/mcp-devtools/</guid></item><item><title>Faster Index I/O with NVMe SSDs</title><link>https://www.marginalia.nu/log/a_123_index_io/</link><description>&lt;p&gt;The Marginalia Search index has been partially rewritten to perform much better, using new data structures designed to make better use of modern hardware. This post will cover the new design, and will also touch upon some of the unexpected and unintuitive performance characteristics of NVMe SSDs when it comes to read sizes.&lt;/p&gt;
&lt;p&gt;The index is already fairly large, but can sometimes feel smaller than it is, and paradoxically, query performance is a big part of why. If each query has a budget of 100-250ms, a design that finds and ranks results faster in that time period will produce better search results. There are other limitations as well, query understanding is still somewhat limited, where only minor changes to a query can unearth dozens of new related results.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 16 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_123_index_io/</guid></item><item><title>On waste</title><link>https://blog.bayindirh.io/blog/on-waste/</link><description>&lt;p&gt;Lately, I have been pondering the waste we generate as we live and the ways to reduce it. As I scale down, I fill trash bags with things I can’t use, which bothers me. The items that can be donated and have a second life are strictly excluded from this definition, though. They’re donated to people who need them.&lt;/p&gt;
&lt;p&gt;The items that are disposed of include cardboard boxes, plastic containers, and items that have leaked or reached the end of their useful life but cannot be consumed, donated, or revived. Putting things in a trash bag and throwing them out is the least preferred thing I do; however, in reality, some things need to go through this system. The wrapping film used to protect a product from the environment can’t be reused in practice, for example.&lt;/p&gt;
&lt;p&gt;It’s imperative to understand that every item has a purpose and useful life, and is rarely useful outside these. While things can be repurposed, achieving this becomes increasingly difficult as time and technology progress. Our current system views products as disposable items, a problem &lt;a href="https://web.archive.org/web/20250720120011/https://english.elpais.com/culture/2025-07-20/the-bewildering-phenomenon-of-declining-quality.html"&gt;El-Pais&lt;/a&gt; recently wrote about. As a result, the products we buy or use have shorter lifespans, and their repairability is exponentially reduced. The discussion of the whys and hows of this phenomenon is worthy of another blog post for another day.&lt;/p&gt;
&lt;p&gt;Humans are creatures of comfort and habit. If something brings us carefree comfort, we tend to cling to it until we need to face the consequences of our actions. This has brought us good things in the past and allowed us to improve our lives progressively; however, we have now passed the point where we need to drastically change our habits. This momentum is evident in our response to climate change. Currently, trying to save our planet is a torturous battle.&lt;/p&gt;
&lt;p&gt;It’s worth noting that there are other factors driving consumption, which are rooted in the society we live in. Humans are social creatures, as are creatures of habit. We have a tendency to buy the things our peers use, wear, or have. Some of us prefer to express ourselves with material items. This creates another vector of excessive consumption. Even others who don’t prefer to do this are manipulated by mass media to buy things by playing on the emotions of owning things.&lt;/p&gt;
&lt;p&gt;Personally, I have begun to view waste as something I need to minimize, both realistically and consciously. This small change had a domino effect on how I perceive consuming and buying things from a bigger perspective. The most significant change I made was reducing disposable items in my life and using items that can be safely and realistically recycled (i.e., glass, metal, paper). This change lends itself effortlessly to buying for life, which advocates for buying better items once and using them for a lifetime.&lt;/p&gt;
&lt;p&gt;Buying for a lifetime is not without its own difficulties, though. The first step is to find something that can endure daily life for a long time, especially when everything else is made to be disposable. In the past, the price of an item was a semi-reliable indicator of its longevity. Now, this line has blurred beyond recognition. Next, assuming that the item exists, its reachability becomes a problem. Many of the higher-quality items are made in small quantities and sold locally, making it harder to obtain them than before. The last point is the price of the item in question. When the first two hurdles are cleared, the cost of the item becomes the final obstacle. The price is higher not only because the item is better made, but the prospective buyer needs to bear more of the fixed costs related to the production of the said item. However, when all hurdles are cleared, what remains is an item that can be used for a long time and passed down to future generations. When used for this long, the item not only pays for itself, but carries and accumulates worth beyond money. If you pardon the term, it becomes enchanted.&lt;/p&gt;
&lt;p&gt;While this is not an easy to path to tread, and sometimes there’s no path, I’m happy and content by trying to reduce what I buy and use them for longer, because having long-lasting items and using them every day without thinking about how to deal with them is akin to wearing a familiar sweater at home and being comfortable.&lt;/p&gt;
&lt;p&gt;And, I like being comfortable, sipping a tea, and remembering good times. It beats trying to keep up with imaginary beings, damaging the environment, and lining somebody else’s pockets big time.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Fri, 15 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/on-waste/</guid></item><item><title>Fun with finite state transducers</title><link>https://blog.yossarian.net/2025/08/14/Fun-with-finite-state-transducers</link><description>I recently1 solved an interesting problem inside zizmor with a type of state machine/automaton I hadn’t used before: a finite state transducer (FST). A few months ago. &amp;#8617;</description><author>ENOSUCHBLOG</author><pubDate>Thu, 14 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/08/14/Fun-with-finite-state-transducers</guid></item><item><title>Set up a single-node EDB Postgres Distributed cluster on Ubuntu</title><link>http://notes.eatonphil.com/2025-08-14-set-single-node-edb-postgres-distributed-cluster-ubuntu.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/set-single-node-edb-postgres-distributed-cluster-ubuntu"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 14 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-08-14-set-single-node-edb-postgres-distributed-cluster-ubuntu.html</guid></item><item><title>Windows XP as the future of AI companies</title><link>https://jodavaho.io/posts/ai-is-just-software-so-sell-it.html</link><description>&lt;p&gt;The Endgame for LLMs Might Look Like Windows XP Hear me out.&lt;/p&gt;
&lt;h2 id="1-the-commercial-space-already-pays-for-heavy-local-tools"&gt;1. The commercial space already pays for heavy, local tools&lt;/h2&gt;
&lt;p&gt;Mechanical engineers pay thousands of dollars per seat for their productivity
software. Those profits go directly into improving the tools. Sometimes there
are cloud add-ons, but the bulk of the functionality is local and offline. The
feature sets are deep: automated FEA, rendering, parts databases. You run this
on powerful machines with serious GPUs, fast CPUs, and large amounts of memory.&lt;/p&gt;
&lt;p&gt;Graphics artists do the same with rendering engines. The money funds better
pipelines, shaders, SDKs for in-house work. All run locally on heavy
hardware.&lt;/p&gt;
&lt;p&gt;Electrical engineers buy Matlab, Simulink, and other specialized packages.
Industrial engineers rely on AutoCAD, LabVIEW, and so on.&lt;/p&gt;
&lt;p&gt;Across disciplines, the pattern is the same:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hardware intensive&lt;/strong&gt; to run&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Indispensable&lt;/strong&gt; for professionals&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Purchased by companies&lt;/strong&gt; that see them as essential to success&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In some cases, like Excel/Word, work software becomes the defacto home software
as well.&lt;/p&gt;
&lt;p&gt;However &amp;hellip;&lt;/p&gt;
&lt;h2 id="2-running-llms-as-a-service-is-expensive"&gt;2. Running LLMs as a service is expensive&lt;/h2&gt;
&lt;p&gt;LLM companies like OpenAI, Anthropic, and others face two big problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The core tech is &lt;em&gt;very&lt;/em&gt; costly to run at scale for end users.&lt;/li&gt;
&lt;li&gt;Building and maintaining the surrounding infrastructure adds even more cost.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I cannot make these arguments as well as &lt;a href="https://blog.kilocode.ai/p/future-ai-spend-100k-per-dev"&gt;this article does&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This limits how cheap and open ended a hosted LLM service can realistically be.
TFA above implies $100,000/y cost to run an &lt;strong&gt;AI&lt;/strong&gt; agent!&lt;/p&gt;
&lt;h2 id="3-a-better-model-license-llms-as-installable-software"&gt;3. A better model: license LLMs as installable software&lt;/h2&gt;
&lt;p&gt;LLMs aren’t Model Ts replacing horses. They’re the &lt;em&gt;internal combustion
engine&lt;/em&gt;, a transformative core technology that powers many different products,
but isn’t a product by itself. Even a chat interface is a &lt;em&gt;product&lt;/em&gt; built on
top of the engine. So, there&amp;rsquo;s really two markets here: LLMs and the LLM-enabled produts.&lt;/p&gt;
&lt;p&gt;AI companies are trying to capture LLM market &lt;em&gt;and&lt;/em&gt; all the product market by
licensing LLM &lt;em&gt;access&lt;/em&gt;. Fine, but they&amp;rsquo;re doing it by also building the
hardware the product runs on. This is fine for a text editor and catastrophic
for a massively intense, insanely popular LLM.&lt;/p&gt;
&lt;p&gt;In the future, Anthropic, OpenAI, Google, and others could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sell custom-made LLMs directly for customer use&lt;/li&gt;
&lt;li&gt;Build a range of software products that run on them&lt;/li&gt;
&lt;li&gt;Separate revenue streams for core tech and end-user tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Imagine deleting the massive inference backend and instead selling a license
for &amp;ldquo;the world’s best coding LLM&amp;rdquo; that you install and run locally. Claude Code
already works fine as a concept. Many tools already support OpenAI style APIs.
Consumer hardware can run small or quantized models today, and
inference-optimized chips are only getting better. NVIDIA can remain king of
&lt;em&gt;training&lt;/em&gt;, while others optimize for &lt;em&gt;running&lt;/em&gt; models.&lt;/p&gt;
&lt;p&gt;You could offer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consumer&lt;/strong&gt; models: smaller, optimized for laptops/desktops&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commercial&lt;/strong&gt; models: larger, more capable, for professional workstations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenSource&lt;/strong&gt; models: For those folks who don&amp;rsquo;t want to pay.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The companies already buying engineering grade PCs could easily justify the
cost, the app ecosystem doesn&amp;rsquo;t have to pay the AI tax, and AI companies can
stop endless capex and focus on building the best LLMs (and maybe some apps).&lt;/p&gt;
&lt;h2 id="4-the-excel-analogy"&gt;4. The Excel analogy&lt;/h2&gt;
&lt;p&gt;If LLMs are sold as &amp;ldquo;commercial offerings to power your local tools,&amp;rdquo; then
someone gets to write the LLM equivalent of Word/Excel. Popular in professional
environments, but with spillover into home use because people want the same
capabilities everywhere.&lt;/p&gt;
&lt;p&gt;And given the broad appeal of LLMs, they could end up embedded in &lt;em&gt;every&lt;/em&gt; work
machine making them as expected as having Excel installed.&lt;/p&gt;
&lt;h2 id="5-the-likely-future-pcsphones-ship-with-ai-core-hwsw"&gt;5. The likely future: PCs+Phones ship with AI core hw/sw&lt;/h2&gt;
&lt;p&gt;The endgame could be a coherent &amp;ldquo;operating system&amp;rdquo; built around licensed LLMs,
used both at work and at home. All your tools like code editors, web apps,
productivity software, would run &lt;em&gt;on&lt;/em&gt; it. You’d pay for the hardware and the
software license, just like today’s PCs and phones.  Apps are built with the
assumption that the OS includes an LLM module to interface with.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Wed, 13 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/ai-is-just-software-so-sell-it.html</guid></item><item><title>How do I get second order effects from these agents?</title><link>https://jodavaho.io/posts/ai-wheres-the-second-order-effects.html</link><description>&lt;p&gt;I use coding agents daily. So far, they are good for first-order effects.
Things like &amp;ldquo;Fast typewriter&amp;rdquo; (doing something I would do but without interface
delays) or &amp;ldquo;super copy paste&amp;rdquo; (altering an example and applying it with nuance
in several places) or &amp;ldquo;super tab complete&amp;rdquo; (suggesting large blocks of
text/code vs just single symbols) or even &amp;ldquo;meta bash&amp;rdquo; (asking for scripts that
do oneoff analysis or processing of a few data sets you have lying around).&lt;/p&gt;
&lt;p&gt;Most of these are code-generation tasks. Sure, we can write code as fast as
possible, but that doesn&amp;rsquo;t help the thinking, decision making, and
understanding go faster. According to Amdahls law, that&amp;rsquo;ll become the blocker,
and probably already has. Theoretically, giving up on understanding is the
natural next step - essentially let agents build whatever they want. Aka vibe
coding.&lt;/p&gt;
&lt;p&gt;So we&amp;rsquo;ve already hit the AI agent first-order improvements limit??&lt;/p&gt;
&lt;p&gt;What I want is &lt;em&gt;second order&lt;/em&gt; effects. I want tooling around my tooling to help
my tooling build my tooling so that I can be more productive and directly
involved. That&amp;rsquo;s the only way to ensure that what &lt;em&gt;I&lt;/em&gt; want comes to pass.&lt;/p&gt;
&lt;p&gt;I want smart-jump 10x, smart-context popups 10x, live execution / simulation,
integrated replay of test cases with backtracking, and a million other things I
can&amp;rsquo;t imagine yet. Instead I get a github agent that automates leaving comments
about my code style. Thanks.&lt;/p&gt;
&lt;p&gt;Anything else is just unproductive laziness and gambling your future job.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Tue, 12 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/ai-wheres-the-second-order-effects.html</guid></item><item><title>A more general implementation of Lamport's Byzantine Consensus algorithm in Python</title><link>https://bytepawn.com/lamport-byzantine-consensus.html</link><description>&lt;p&gt;This follow-up factors Lamport’s Byzantine Generals algorithm into a generalized class that's independent of I/O and payloads. Any node can initiate a round (be "king"), values can be arbitrary strings, and multiple rounds can run concurrently.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-flute-python.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Tue, 12 Aug 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/lamport-byzantine-consensus.html</guid></item><item><title>Assessing students in the era of AI</title><link>https://austinhenley.com/blog/aihomework.html</link><description>&lt;a href="https://austinhenley.com/blog/aihomework.html"&gt;https://austinhenley.com/blog/aihomework.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 10 Aug 2025 21:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/aihomework.html</guid></item><item><title>It was time for a dim bulb current limiter</title><link>http://blog.jgc.org/2025/06/it-was-time-for-dim-bulb-current-limiter.html</link><description>&lt;p&gt;One of my Minitels (the one that I &lt;a href="https://blog.jgc.org/2022/03/voiding-warranty-on-1993-minitel-2-to.html"&gt;modified a few years ago to run new firmware&lt;/a&gt;) started having power supply problems: the LED was on but nothing else was working. I suspected that the main power/CRT board needed new capacitors and so I recapped it:&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh4RXdLhCqZuNiwHrSNNjV6Xiox_NxujebIDyWmdiHuEGocDo5YyW0BpRb7Hw9JuY0gsP302QXsDIhXA-LvC4PFRK6wTxQcnGbDa6VOxMeaeP_pa867CVka1tf6yL0q1qJ07Bqjp8kFOBAUOjth6FWKcvSap9k0jM07sbT_Hyiq1Phzccq831jcA/s1707/dim-bulb-5.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh4RXdLhCqZuNiwHrSNNjV6Xiox_NxujebIDyWmdiHuEGocDo5YyW0BpRb7Hw9JuY0gsP302QXsDIhXA-LvC4PFRK6wTxQcnGbDa6VOxMeaeP_pa867CVka1tf6yL0q1qJ07Bqjp8kFOBAUOjth6FWKcvSap9k0jM07sbT_Hyiq1Phzccq831jcA/w480-h640/dim-bulb-5.jpeg" width="480" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Alas, despite some of the older capacitors measuring poorly compared to their specs, that wasn't the problem and so I need to go deeper. The only problem is there are a ton of nasty voltages on this board. Notably the 230V AC input and then the tens of kilovolts generated for the CRT.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Also the board seems to have a short somewhere.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, it's time to a dim bulb current limiter. Which I didn't have. So I made one. Here it is:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRtE5ZfCmta06KSLsOktQK2C2mCfbVs_T-dcnX7rvarawQzFsfqykDPLUR_AfPfMZUdCQx6RAmSPwjyjDUgwUBfh_VIpGKZvMRwM30CCHViyZBgMKiPm4eTkAwdVNPLeSa4TptwEdO3amheh-3AKvYT-xSJF2omTVX6SAned-UIZwujBag9dHkwA/s1280/dim-bulb-2.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRtE5ZfCmta06KSLsOktQK2C2mCfbVs_T-dcnX7rvarawQzFsfqykDPLUR_AfPfMZUdCQx6RAmSPwjyjDUgwUBfh_VIpGKZvMRwM30CCHViyZBgMKiPm4eTkAwdVNPLeSa4TptwEdO3amheh-3AKvYT-xSJF2omTVX6SAned-UIZwujBag9dHkwA/w640-h480/dim-bulb-2.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;It's a remarkably simple device which plugs into a standard power outlet (230V AC) and puts a lightbulb in series with one of the connections. The two wires coming in then go to a standard socket. Thus you plug your device (e.g. my Minitel) into the socket and the lightbulb limits the current.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These things were super &lt;a href="https://antiqueradio.org/dimbulb.htm"&gt;common in the past&lt;/a&gt; for anyone working with mains voltages.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Works great if you have an incandescent bulb at, say, 100W. A 100W bulb on 230V AC would have a current running through it of I = V/P -&amp;gt; I = 100/230 (approximately 430mA). Limiting the current that way means you're less likely to damage the device you're working on if there's a short. Naturally, using a different bulb would give you a different current. 60W would give you around 230mA. 150W around 650mA.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As the Minitel's input fuse is 630mA, my current limiter will keep things well under its blowing current. Also, if there's a complete short then the bulb will glow brightly.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here's a look inside:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhia865PgNjaxbIC7I-HSdzyRt58mvvTzlFuihJVfM6R5HltlT6Y-THJ0FyUtHrbmq5EDpw3a9fDW14mi5LS-mhYMx-OcjeYo6dX_sIO7YDKdkegwTvZLxAHW6MyPx5c1O1S5v5HTmNoB5JrAghZWKTy_iaWblrup62OEdrFskWAK78qPQBrJKz5A/s1280/dim-bulb-1.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhia865PgNjaxbIC7I-HSdzyRt58mvvTzlFuihJVfM6R5HltlT6Y-THJ0FyUtHrbmq5EDpw3a9fDW14mi5LS-mhYMx-OcjeYo6dX_sIO7YDKdkegwTvZLxAHW6MyPx5c1O1S5v5HTmNoB5JrAghZWKTy_iaWblrup62OEdrFskWAK78qPQBrJKz5A/w640-h480/dim-bulb-1.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;Power comes in at the top, and goes to a DPST switch. I'm using DPST because it's not easy to know which input wire is live and which is neutral because the European &lt;a href="https://en.wikipedia.org/wiki/Schuko"&gt;Schuko/Type F&lt;/a&gt;&amp;nbsp;plug/socket is not polarized and I like knowing that when I switch something off, it's off! And the DPST has an integrated lamp that gives me a visual reminder that it's on.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtd5yKvaoR4kCaz0wCPJpM0SMV3toUG5kbirF88W3Rc3XwvRPT93gkPx9u62UmdXLKOMH64bBa0pdpWwtKPwDgXK1lBNNbVEccd_PQ3it3orfVqcIA6HKzwoO3sOsdd-9ZTk0TSPxhYQhyTKTHb-HTzqNseLad2i_vJvumQks3mTjwSeaGPInG5A/s1964/dim-bulb-7.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtd5yKvaoR4kCaz0wCPJpM0SMV3toUG5kbirF88W3Rc3XwvRPT93gkPx9u62UmdXLKOMH64bBa0pdpWwtKPwDgXK1lBNNbVEccd_PQ3it3orfVqcIA6HKzwoO3sOsdd-9ZTk0TSPxhYQhyTKTHb-HTzqNseLad2i_vJvumQks3mTjwSeaGPInG5A/w640-h132/dim-bulb-7.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because local power sockets aren't polarised, the power strip I am using for my bench gear uses DPST switches for each socket. It's relatively hard to find one that does this (and says it does). Thanks, &lt;a href="https://www.brennenstuhl.com/en-PT/products/extension-leads/extension-lead-individually-switchable-6-way-2m-h05vv-f-3g1.5-white"&gt;Brennenstuhl&lt;/a&gt;!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFVAd90qESyXhysVOcMSgX7Qx8fx9qacoGkZJiOlcmHjSjr5iVfl75nxxB1HFk7i0JZ6IYn3qMEDlBpx3gdXXBZr8jgLx8FydXZZri3SU6mBhtyjr7qQ5rxiTuZ2XbKrhYklDwOb5ciD1nl1uk-0SDvBULfuVZt-Tw4LLwFpZlfxfHy9gp1WLK5A/s3488/dim-bulb-6.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="290" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFVAd90qESyXhysVOcMSgX7Qx8fx9qacoGkZJiOlcmHjSjr5iVfl75nxxB1HFk7i0JZ6IYn3qMEDlBpx3gdXXBZr8jgLx8FydXZZri3SU6mBhtyjr7qQ5rxiTuZ2XbKrhYklDwOb5ciD1nl1uk-0SDvBULfuVZt-Tw4LLwFpZlfxfHy9gp1WLK5A/w640-h290/dim-bulb-6.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;But I digress.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To make my dim bulb current limiter do the right thing, I need a 100W incandescent bulb. These have been on their way to oblivion in the EU since 2009 and were, I believe, eliminated for home use in 2016. It is still possible to buy them, labelled for "industrial use", from some locations.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJDOISKREffD-X0vDdc0QVCLXF4JmXDY5F7FDEsGzIOhZppJCmDGN8w-zcrlzAjn2H3pOJIXD7Klw0dcf5JIKo2iswFGPpaW42BopFhV_Q4TAiV8rp7qjierBrivRQF-Dt5FgiXGizc6PC4-1vT6OSASPzES4Ek-OK2XYZbyYfi7fNN8kik1BTzg/s1280/dim-bulb-3.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="470" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJDOISKREffD-X0vDdc0QVCLXF4JmXDY5F7FDEsGzIOhZppJCmDGN8w-zcrlzAjn2H3pOJIXD7Klw0dcf5JIKo2iswFGPpaW42BopFhV_Q4TAiV8rp7qjierBrivRQF-Dt5FgiXGizc6PC4-1vT6OSASPzES4Ek-OK2XYZbyYfi7fNN8kik1BTzg/w640-h470/dim-bulb-3.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, that limits the current and protects the device I'm working on, but what about protecting me? For that, I pair it with an isolating transformer. If you're not familiar with this then here's the basics. In a house the neutral line is connected to ground (typically at the point at which power comes into your house). This means that the 230V coming out the live wire is relative to ground. If I touch it the power will flow through me to ground.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The isolating transformer eliminates the link between live, neutral and ground. It's still outputting 230V but that voltage isn't relative to ground and so I won't get a shock if I touch one of the wires coming from the transformer's socket. Of course, touching both would send 230V through me.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwdEB4jBV-xo2lO_q8nigbRkTTGvkTVYQRtDyKVxqcrCScxhWyEKZQ7oSjgvishX4GFXjqzZ0KezPQrzTEGGFgNNUtRFW58SQ4UzTWEjtD83tUdDm2SlkWwGk3DrHTZZ-zD8ibRiWnNESCeA1OJJLLsahrkdb8O_LMy4jfHo5TP6Db-4zPdkeGEQ/s1280/dim-bulb-4.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="474" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwdEB4jBV-xo2lO_q8nigbRkTTGvkTVYQRtDyKVxqcrCScxhWyEKZQ7oSjgvishX4GFXjqzZ0KezPQrzTEGGFgNNUtRFW58SQ4UzTWEjtD83tUdDm2SlkWwGk3DrHTZZ-zD8ibRiWnNESCeA1OJJLLsahrkdb8O_LMy4jfHo5TP6Db-4zPdkeGEQ/w640-h474/dim-bulb-4.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;So, having built that I can go back to understanding why my Minitel isn't powering on.&lt;/div&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Sun, 10 Aug 2025 20:21:04 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/06/it-was-time-for-dim-bulb-current-limiter.html</guid></item><item><title>Becoming a Student Again</title><link>https://benovermyer.com/blog/2025/08/becoming-a-student-again/</link><description>&lt;p&gt;Early this year, I started applying to universities in Canada for master's degree programs. In March, I was accepted to the Master of Software Engineering program at Memorial University in St. John's, Newfoundland.&lt;/p&gt;
&lt;p&gt;From that point forward, we began the planning process for a major life shift. In early July, we gave notice at our jobs, and our long journey to move to Canada became mostly public knowledge. I say "mostly" because I have been very quiet about it online. Until it was a real, for-sure thing and we wouldn't have to pivot, I didn't want to make it known more widely.&lt;/p&gt;
&lt;p&gt;Well, on August 8 we crossed the border in Maine and formally got my study permit and Sarah's work permit. We are now officially temporary residents of Canada. Unless something catastrophic happens, we'll be here at least until I finish my degree, which means 2027 at the earliest.&lt;/p&gt;
&lt;p&gt;So. That means I will be a university student for the first time since 2007, when I got my bachelor's degree in history at South Dakota State University. This time around, I will be a graduate student.&lt;/p&gt;
&lt;p&gt;I'm really excited. I get to reinvent my life and maybe, depending on how I like the academic life, start a new career. At the very least, we get to escape the growing darkness in the United States.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 09 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/08/becoming-a-student-again/</guid></item><item><title>Rigging-suspended installation of a marine wind generator</title><link>https://bergie.iki.fi/blog/rigging-suspended-wind-generator/</link><description>&lt;p&gt;We cruise on a small boat, a &lt;a href="https://lille-oe.de/boat/"&gt;31ft double-ender&lt;/a&gt;. As we’re off-grid the vast majority of the time, all electricity needs to be produced from renewable sources. Solar produces a lion’s share, but other sources are needed for overcast days. We don’t have space for a permanently mounted wind generator, so we converted a &lt;a href="https://www.superwind.com/en/"&gt;Superwind 350&lt;/a&gt; to rigging-suspended.&lt;/p&gt;

&lt;h2 id="why-wind"&gt;Why wind?&lt;/h2&gt;

&lt;p&gt;As our &lt;a href="https://lille-oe.de/2024/"&gt;2024 cruise&lt;/a&gt; to the not-so-sunny Scotland demonstrated, there would still be place for wind power in the renewables mix of a long-distance sailboat. My &lt;a href="https://gist.github.com/bergie/d0eda471e3774b0cb3b49e33853394d1"&gt;energy production simulations&lt;/a&gt; from 2023 also showed a lot of promise for wind power.&lt;/p&gt;

&lt;p&gt;Our boat has a canoe stern and dual aft stays, meaning that there is not much space in the back of the boat.
We had a conversation with Superwind back in 2022, and they were of the opinion that there simply isn’t a good space for installing one.
And so we &lt;a href="https://lille-oe.de/2023-04-14/"&gt;installed a hydrogenerator&lt;/a&gt; and decided that we’d go sailing if we ran out of power.&lt;/p&gt;

&lt;p&gt;But the interest in wind generators remained.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Ampair 100 at Sainte-Anne" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/20250125_172524.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;And then one day, rowing around the Sainte-Anne anchorage in rainy Martinique, I saw a potential solution: one of the small cruising boats had a wind generator suspended from ropes in their foretriangle. I chatted a bit with the owners, and they confirmed that the system worked nicely. Time for some research!&lt;/p&gt;

&lt;h2 id="rigging-suspended-wind-generator"&gt;Rigging-suspended wind generator&lt;/h2&gt;

&lt;p&gt;Rigging-suspended wind generators used to be common. Commercial models included the Ampair 100, WindBugger, and the Hamilton-Ferris. As solar power has become cheaper, wind generators in general have fallen out of favor. At the same time cruising boats have grown in size, enabling permanent mounting of a big wind turbine.&lt;/p&gt;

&lt;p&gt;These effects combined mean that there are currently no commercial manufacturers of rigging-suspended wind generators for boats.&lt;/p&gt;

&lt;p&gt;Of the models once manufactured, the Ampair 100 sounded especially promising. It was a modular system that could be used either as a rigging-suspended wind turbine, or as a “tow generator” for making power while under sail.&lt;/p&gt;

&lt;p&gt;This modularity is a big advantage of a rigging-suspended wind generator, especially for ease of stowing. They can also be a lot quieter than the pole-mounted ones, as any vibrations are dampened by the suspension ropes. And of course they don’t cause any windage or shading while stowed.&lt;/p&gt;

&lt;p&gt;I tried finding an Ampair for sale online with no luck. The second-hand chandlery in Grenada – &lt;a href="https://treasuretrove.shop"&gt;Treasure Trove&lt;/a&gt; – had two units, but couldn’t locate the wind blades.&lt;/p&gt;

&lt;p&gt;However, the wind generator market has evolved quite a bit. There are several good wind generators intended for permanent mounting. &lt;a href="https://www.superwind.com/en/applications/sailing"&gt;Superwind&lt;/a&gt; and the &lt;a href="https://eclectic-energy.co.uk/products/d400-wind-generator/"&gt;D400&lt;/a&gt; provide the best alternatives, but are very expensive. On the cheap end, there are numerous Chinese wind generators from companies like &lt;a href="https://www.pikasola.com"&gt;Pikasola&lt;/a&gt; and &lt;a href="https://www.vevor.com/s/wind-turbine"&gt;Vevor&lt;/a&gt; starting at around $250.&lt;/p&gt;

&lt;p&gt;Maybe I could design a bracket to convert one of these for rigging-suspended installation?&lt;/p&gt;

&lt;h2 id="building-the-bracket"&gt;Building the bracket&lt;/h2&gt;

&lt;p&gt;Sitting in the windy anchorage at Spanish Water this idea started sounding more and more interesting. After some paper brainstorming, I grabbed &lt;a href="https://www.freecad.org"&gt;FreeCAD&lt;/a&gt; and made an initial design.&lt;/p&gt;

&lt;p&gt;The design parameters were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Can be built somewhat cheaply by a local metal fabricator&lt;/li&gt;
  &lt;li&gt;Can facilitate the most common fixed-mount wind generators&lt;/li&gt;
  &lt;li&gt;Poles to keep the rigging lines clear of the propellers&lt;/li&gt;
  &lt;li&gt;Wind generator is held in place and the whole assembly turns into the wind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My original idea was a neat stainless ring around the wind generator body. However, different wind generators are of different height, and so in interests of both manufacturing cost and adaptability, I went with two brackets connected by threaded rod.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Wind turbine bracket design" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/wind-bracket.png" /&gt;&lt;/p&gt;

&lt;p&gt;It took a couple of months to actually get a quote from a local fabricator, but now we finally have the finished brackets for ourselves and a neighboring boat.&lt;/p&gt;

&lt;p&gt;You can find the FreeCAD file &lt;a href="https://github.com/meri-imperiumi/lille-oe/raw/refs/heads/main/hardware/Windgenerator%20bracket.FCStd"&gt;on GitHub&lt;/a&gt;. There are also STEP files for the &lt;a href="https://github.com/meri-imperiumi/lille-oe/blob/main/hardware/Windgenerator%20bracket-AssemblyBracket%20top.step"&gt;top bracket&lt;/a&gt; and the &lt;a href="https://github.com/meri-imperiumi/lille-oe/blob/main/hardware/Windgenerator%20bracket-AssemblyBracket%20bottom.step"&gt;bottom bracket&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;

&lt;p&gt;Our neighbor installed a 400W Pikasola wind generator on theirs. That mounted on the bracket without any other adapting except for some rubber mat to isolate the stainless parts from the aluminium wind turbine body.&lt;/p&gt;

&lt;p&gt;We had bought an old Superwind 350 from another boat, and so for us a small connecting piece (140mm long aluminium pipe with 55mm inner diameter) was needed to make that fit.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Wind generator bracket adapted for Superwind 350" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/20250807_125654.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;The wind generator is hoisted using our spinnaker pole topping lift, with a short strop riding on the inner forestay. Stabilization is with a three rope bridle connected to pad eyes on the deck.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Deployed rigging-suspended Superwind 350" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/20250809_085537.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;We have wires running from the bottom bracket to the deck level, where they connect via MC4 connectors to to cables running to inside the boat. This way we can easily disconnect the wind turbine as needed. We are adding a stop/run switch soon as well to aid deployment.&lt;/p&gt;

&lt;p&gt;Deployment is already documented &lt;a href="https://handbook.lille-oe.de/systems/electrics/#superwind"&gt;in our boat handbook&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We only got the Superwind deployed yesterday evening, and so we are gathering the early experiences. However, right now we’re on track to producing about 0.6kWh on the first day. This is measured with a dedicated Victron SmartShunt wired to the wind generator regulator and logging into our time series database.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Wind generator as seen in Venus OS" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/wind-generator-venus.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;That 0.6kWh per day is like a whole second solar arch!&lt;/p&gt;

&lt;p&gt;Noise levels are not too bad at all. Inside the boat you can’t hear anything. In cockpit, you can hear a slight whirr from the generator, but it is a lot quieter than one of the popular pole-mounted wind turbines on neighboring boat, heard from few hundred meters away.&lt;/p&gt;

&lt;p&gt;Durability and handling of heavier winds will remain to be seen. As will the practicality of stowing and deploying when changing anchorages. Though we already do similar things with the mast-hoisted solar panels and the &lt;a href="https://lille-oe.de/dinghy/"&gt;nesting dinghy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Especially when going with one of the cheap Chinese models, this rigging-suspended method can be the way to add wind power to a boat in an affordable way. We calculated the total price for the Pikasola installation to be around the same as what marine wind generator manufacturers ask for just a mounting pole!&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/meri-imperiumi/lille-oe/tree/main/hardware"&gt;hardware design&lt;/a&gt; should be quite easy to manufacture anywhere where you can find a stainless steel welder. After all, we were able to get ours fabricated on a tropical island.&lt;/p&gt;

&lt;p&gt;For us the new wind generator can be seen as completing the circle of our deployable renewable options:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;When under sail, power is generated with the hydrogenerator&lt;/li&gt;
  &lt;li&gt;When anchored in light winds, power is generated with the mast-hoisted &lt;a href="https://flin-solar.com"&gt;FLINsail&lt;/a&gt; solar array&lt;/li&gt;
  &lt;li&gt;When anchored in heavier winds, power is generated by the rigging-suspended Superwind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of these we have the fixed solar panels.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Lille Ø at anchor" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/20250807_200722.jpg" /&gt;&lt;/p&gt;</description><author>Henri Bergius</author><pubDate>Sat, 09 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://bergie.iki.fi/blog/rigging-suspended-wind-generator/</guid></item><item><title>What even is distributed systems</title><link>http://notes.eatonphil.com/2025-08-09-what-even-is-distributed-systems.html</link><description>&lt;p&gt;Distributed systems is simply the study of interactions between
processes. Every two interacting processes form a distributed system,
whether they are on the same host or not. Distributed systems create
new challenges (compared to single-process systems) in terms of
correctness (i.e. &lt;em&gt;consistency&lt;/em&gt;), reliability, and performance
(i.e. latency and throughput).&lt;/p&gt;
&lt;p&gt;The best way to learn about the principles and fundamentals of
distributed systems is to 1) read Designing Data Intensive
Applications and 2) read through the papers and follow the notes in
the &lt;a href="https://pdos.csail.mit.edu/6.824/schedule.html"&gt;MIT Distributed Systems
course&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For Designing Data Intensive Applications (DDIA), I strongly encourage
you to find buddies at work or online who will read it through with
you. You can also always join the &lt;a href="https://eatonphil.com/discord.html"&gt;Software Internals
Discord&lt;/a&gt;'s #distsys channel to
ask questions as you go. But it's still best if you have some partners
to go through the book with, even if they are as new to it as you.&lt;/p&gt;
&lt;p&gt;I also used to think that you might want to wait a few years into your
career before reading DDIA but when you have friends to read it with I
think you need not wait.&lt;/p&gt;
&lt;p&gt;If you have only skimmed the book you should definitely go back and
give it a thorough read. I have read it three times already and I will
read it again as part of the &lt;a href="https://eatonphil.com/bookclub.html"&gt;Software Internals Book
Club&lt;/a&gt; next year after the 2nd
Edition is published.&lt;/p&gt;
&lt;p&gt;Keep in mind that every chapter of DDIA provides references to papers
you can keep reading should you end up memorizing DDIA itself.&lt;/p&gt;
&lt;p&gt;When you've read parts of DDIA or the MIT Distributed Systems course
and you want practice, the Fly.io x Jepsen &lt;a href="https://fly.io/dist-sys/"&gt;Distributed Systems
Challenge&lt;/a&gt; is one guided option. Other
options might include simply implementing (getting progressively more
complex down the list):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;two-phase commit  &lt;/li&gt;
&lt;li&gt;three-phase commit  &lt;/li&gt;
&lt;li&gt;single-decree Paxos&lt;/li&gt;
&lt;li&gt;highly available key-value store on top of a 3rd-party consensus library&lt;/li&gt;
&lt;li&gt;chain replication (or CRAQ), using a 3rd-party consensus library  &lt;/li&gt;
&lt;li&gt;Raft&lt;/li&gt;
&lt;li&gt;epaxos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you get bored there you can see Alex Miller's &lt;a href="https://transactional.blog/blog/2024-data-replication-design-spectrum"&gt;Data Replication
Design
Spectrum&lt;/a&gt;
for more ideas and variants.&lt;/p&gt;
&lt;p&gt;And if you want more people to follow, check out the &lt;a href="https://eatonphil.com/blogs.html#Distributedsystems"&gt;Distributed
Systems section&lt;/a&gt;
of my favorite blogs page.&lt;/p&gt;
&lt;p&gt;If these projects and papers sound arcane or intimidating, know that
you will see the problems these projects/papers solve whether or not
you know and understand these solutions. Developers often end up
reinventing hacky versions of these which are more likely to have
subtle bugs.&lt;/p&gt;
&lt;p&gt;While instead you can recognize and use one of these well-known
building blocks. Or at least have the background to better reason
about correctness should you be in a situation where you must work
with a novel distributed system or you end up designing a new one
yourself.&lt;/p&gt;
&lt;p&gt;And again, if you want folks to bounce ideas off of or ask questions
to, I strongly encourage you to join the Software Internals Discord
and ask there!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a short post on learning the fundamentals of distributed systems, with a few suggested resources to read and a few suggested projects to try. &lt;a href="https://t.co/b0EhDP8K0t"&gt;pic.twitter.com/b0EhDP8K0t&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1954196346199519365?ref_src=twsrc%5Etfw"&gt;August 9, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 09 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-08-09-what-even-is-distributed-systems.html</guid></item><item><title>p-fast trie, but smaller</title><link>https://dotat.at/@/2025-08-06-p-fast-trie.html</link><description>&lt;p&gt;Previously, I wrote &lt;a href="https://dotat.at/@/2025-08-04-p-fast-trie.html"&gt;some sketchy ideas for what I call a p-fast
trie&lt;/a&gt;, which is basically a wide fan-out variant of an x-fast
trie. It allows you to find the longest matching prefix or nearest
predecessor or successor of a query string in a set of names in
O(log k) cache misses, where k is the key length.&lt;/p&gt;
&lt;p&gt;My initial sketch was more complicated and greedy for space than
necessary, so here’s a simplified revision.&lt;/p&gt;
&lt;p&gt;(“p” now stands for prefix.)&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-06-p-fast-trie.html#layout" name="layout"&gt;layout&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A p-fast trie stores a lexicographically ordered set of names.&lt;/p&gt;
&lt;p&gt;A name is a sequence of characters from some small-ish character set.
For example, DNS names can be represented as a set of about 50
letters, digits, punctuation and escape characters, usually one per
byte of name. Names that are arbitrary bit strings can be split into
chunks of 6 bits to make a set of 64 characters.&lt;/p&gt;
&lt;p&gt;Every unique prefix of every name is added to a hash table.&lt;/p&gt;
&lt;p&gt;An entry in the hash table contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A shared reference to the closest name lexicographically greater
than or equal to the prefix.&lt;/p&gt;
&lt;p&gt;Multiple hash table entries will refer to the same name. A
reference to a name might instead be a reference to a leaf object
containing the name.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The length of the prefix.&lt;/p&gt;
&lt;p&gt;To save space, each prefix is not stored separately, but implied
by the combination of the closest name and prefix length.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A bitmap with one bit per possible character, corresponding to the
next character after this prefix.&lt;/p&gt;
&lt;p&gt;For every other prefix that matches this prefix and is one
character longer than this prefix, a bit is set in the bitmap
corresponding to the last character of the longer prefix.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-06-p-fast-trie.html#search" name="search"&gt;search&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The basic algorithm is a longest-prefix match.&lt;/p&gt;
&lt;p&gt;Look up the query string in the hash table. If there’s a match, great,
done.&lt;/p&gt;
&lt;p&gt;Otherwise proceed by binary chop on the length of the query string.&lt;/p&gt;
&lt;p&gt;If the prefix isn’t in the hash table, reduce the prefix length and
search again. (If the empty prefix isn’t in the hash table then there
are no names to find.)&lt;/p&gt;
&lt;p&gt;If the prefix is in the hash table, check the next character of the
query string in the bitmap. If its bit is set, increase the prefix
length and search again.&lt;/p&gt;
&lt;p&gt;Otherwise, this prefix is the answer.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-06-p-fast-trie.html#predecessor" name="predecessor"&gt;predecessor&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Instead of putting leaf objects in a linked list, we can use a more
complicated search algorithm to find names lexicographically closest
to the query string. It’s tricky because a longest-prefix match can
land in the wrong branch of the implicit trie. Here’s an outline of a
predecessor search; successor requires more thought.&lt;/p&gt;
&lt;p&gt;During the binary chop, when we find a prefix in the hash table,
compare the complete query string against the complete name that the
hash table entry refers to (the closest name greater than or equal to
the common prefix).&lt;/p&gt;
&lt;p&gt;If the name is greater than the query string we’re in the wrong branch
of the trie, so reduce the length of the prefix and search again.&lt;/p&gt;
&lt;p&gt;Otherwise search the set bits in the bitmap for one corresponding to
the greatest character less than the query string’s next character; if
there is one remember it and the prefix length. This will be the top
of the sub-trie containing the predecessor, unless we find a longer
match.&lt;/p&gt;
&lt;p&gt;If the next character’s bit is set in the bitmap, continue searching
with a longer prefix, else stop.&lt;/p&gt;
&lt;p&gt;When the binary chop has finished, we need to walk down the
predecessor sub-trie to find its greatest leaf. This must be done one
character at a time – there’s no shortcut.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-06-p-fast-trie.html#thoughts" name="thoughts"&gt;thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In my previous note I wondered how the number of search steps in a
p-fast trie compares to a qp-trie.&lt;/p&gt;
&lt;p&gt;I have some old numbers measuring the average depth of &lt;a href="https://dotat.at/prog/qp/blog-2015-10-19.html"&gt;binary, 4-bit,
5-bit, 6-bit&lt;/a&gt; and &lt;a href="https://dotat.at/prog/qp/blog-2020-07-05.html"&gt;4-bit, 5-bit, dns&lt;/a&gt; qp-trie variants. A
DNS-trie varies between 7 and 15 deep on average, depending on the
data set. The number of steps for a search matches the depth for
exact-match lookups, and is up to twice the depth for predecessor
searches.&lt;/p&gt;
&lt;p&gt;A p-fast trie is at most 9 hash table probes for DNS names, and
unlikely to be more than 7. I didn’t record the average length of
names in my benchmark data sets, but I guess they would be 8–32
characters, meaning 3–5 probes. Which is far fewer than a qp-trie,
though I suspect a hash table probe takes more time than chasing a
qp-trie pointer. (But this kind of guesstimate is notoriously likely
to be wrong!)&lt;/p&gt;
&lt;p&gt;However, a predecessor search might need 30 probes to walk down the
p-fast trie, which I think suggests a linked list of leaf objects is a
better option.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Wed, 06 Aug 2025 20:20:42 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-08-06-p-fast-trie.html</guid></item><item><title>My 2025 New Mac Setup</title><link>https://www.swyx.io/new-mac-setup</link><description>&lt;p&gt;I set up a new Mac for work today. Here's everything I use on a Mac for fullstack web development.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 06 Aug 2025 18:27:30 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/new-mac-setup</guid></item><item><title>Losslessly Changing Video Framerate</title><link>https://kevincox.ca/2025/08/05/losslessly-change-video-framerate/</link><description>&lt;p style="margin: 0; padding: 0;"&gt;I’ve been taking a few timelapse videos recently and as an ammeter I’ve just been using the default phone camera apps. Google Camera defaults to “Auto” for the speedup, which keeps the resulting video between 15 and 30s no matter how long you record for (with minimum of 5x speed). It also offers explicit 5, 10, 30 and 120x speeds. The iPhone forgoes explicit options and only offers an auto mode with 20-40s output target and a 20x minimum speed.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;Both of these apps have a limitation that they can only output 30 fps video. The seems like a missed opportunity since the faster than real time playback already tends to produce jumpy video. Getting 60 fps (or even 120 fps) makes a huge improvement to smoothness. The hardware can easily support these output framerates as the capture framerate will still be lower than regular speed videos.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;My preferred approach is to record the video at “twice the duration” as I actually want, then to speed it up after the fact. Recording at a higher input framerate always gives you more flexibility as you can always speed it up later, and if you carefully pick your input framerate you can do this losslessly to avoid degrading the video quality.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;For example, if I want to end up with a 1 minute 60 fps video covering 1 hour of real time. I need to record at &lt;span class="katex" style="margin: 0; padding: 0;"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;60&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mi mathvariant="normal"&gt;s&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;60&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mrow&gt;&lt;mi mathvariant="normal"&gt;H&lt;/mi&gt;&lt;mi mathvariant="normal"&gt;z&lt;/mi&gt;&lt;/mrow&gt;&lt;mo&gt;÷&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mi mathvariant="normal"&gt;h&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;60\u{s} \times 60\u{Hz} \div 1\u{h}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;which is 1 fps. Google Camera doesn’t show you the framerate directly, but you can easily convert the input speeds to framerates. Since it always expects to output 30 fps the calculation is just &lt;span class="katex" style="margin: 0; padding: 0;"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;30&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mrow&gt;&lt;mi mathvariant="normal"&gt;f&lt;/mi&gt;&lt;mi mathvariant="normal"&gt;p&lt;/mi&gt;&lt;mi mathvariant="normal"&gt;s&lt;/mi&gt;&lt;/mrow&gt;&lt;mo&gt;÷&lt;/mo&gt;&lt;mtext&gt;speed&lt;/mtext&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;30\u{fps} \div \v{speed}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;, so to capture at 1 fps I need to select 30x speed.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;To losslessly convert the video you can use ffmpeg bitstream filters.&lt;/p&gt;&lt;pre class="_1"&gt;&lt;code class="_2 language-sh"&gt;ffmpeg
	-i input-video.mp4 &lt;span class="_3"&gt;# Input file.&lt;/span&gt;
	-c copy &lt;span class="_3"&gt;# Don't transcode, remain lossless.&lt;/span&gt;
	-r 60 &lt;span class="_3"&gt;# Output framerate, this is mostly informational for the whole-video metadata.&lt;/span&gt;
	-fps_mode vfr &lt;span class="_3"&gt;# Don't drop frames to match the expected framerate, just preserve all input frames.&lt;/span&gt;
	-bsf:v &lt;span class="_4"&gt;&lt;span class="_4"&gt;"&lt;/span&gt;setts=ts=PTS/2&lt;span class="_4"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="_3"&gt;# Update the presentation time to 1/2 the previous one (play twice as fast).&lt;/span&gt;
	output.mp4 &lt;span class="_3"&gt;# Output file.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p style="margin: 0; padding: 0;"&gt;This can easily be adjusted to other framerates by changing the &lt;code class="_2 language-text"&gt;-r&lt;/code&gt; and &lt;code class="_2 language-text"&gt;-bsf:v&lt;/code&gt; values.&lt;/p&gt;&lt;aside class="_5"&gt;&lt;b style="margin: 0; padding: 0;"&gt;Note&lt;/b&gt;&lt;p style="margin: 0; padding: 0;"&gt;The ffmpeg wiki has a page &lt;a class="_6" href="https://trac.ffmpeg.org/wiki/How%20to%20speed%20up%20/%20slow%20down%20a%20video"&gt;How to speed up / slow down a video&lt;/a&gt;. However this page &lt;a class="_6" href="https://trac.ffmpeg.org/wiki/How%20to%20speed%20up%20/%20slow%20down%20a%20video#rawbitstreammethod"&gt;only has a lossless method for h264 and h265&lt;/a&gt;. Furthermore this method makes it very difficult to preserve metadata, don’t handle variable frame rate properly and is multiple steps. I have found my method to be much easier and more effective.&lt;/p&gt;&lt;/aside&gt;</description><author>Kevin Cox's Blog</author><pubDate>Wed, 06 Aug 2025 03:05:00 GMT</pubDate><guid isPermaLink="true">https://kevincox.ca/2025/08/05/losslessly-change-video-framerate/</guid></item><item><title>Revisiting the Kingdoms of Stone and Fire</title><link>https://benovermyer.com/blog/2025/08/revisiting-the-kingdoms-of-stone-and-fire/</link><description>&lt;p&gt;I haven't touched tabletop role-playing games in a few months now. In preparation for my upcoming move to Canada, I sold most of my collection. I didn't expect to even think about these games until well after the move.&lt;/p&gt;
&lt;p&gt;Then, I found &lt;a href="https://typst.app/" rel="external"&gt;Typst&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Typst is a computer language for laying out books and documents. It's similar in concept to &lt;a href="https://www.latex-project.org/" rel="external"&gt;LaTeX&lt;/a&gt;, a common tool for academics. In fact, I have been using LaTeX to lay out all of my role-playing games for several years now. However, despite its ubiquity, LaTeX is pretty terrible to work with. It's slow and hard to extend. So, when I saw that a newcomer to the field of layout tools was getting rave reviews on Hacker News, I thought I would give it a try.&lt;/p&gt;
&lt;p&gt;So, I chose to rework &lt;a href="https://benovermyer.com/work/rpg/vox-draconis-ksf/"&gt;Vox Draconis: Kingdoms of Stone and Fire&lt;/a&gt; into Typst instead of LaTeX. The result was awesome, and I adore the toolset. It was much easier to use.&lt;/p&gt;
&lt;p&gt;What I didn't expect as a result of this experiment, though, was that it rekindled my interest in working on Kingdoms of Stone and Fire.&lt;/p&gt;
&lt;p&gt;With the final death of Silver Gryphon Games last year, I did not expect to ever pick up game design again. Now I'm seeing all kinds of possibilities. I think my mistake was attempting to make a commercially-viable game. All of the different disciplines of marketing, finances, and so forth pulled me away from the thing I actually liked to do - design.&lt;/p&gt;
&lt;p&gt;So. The PDF of Kingdoms of Stone and Fire that is on this site now is what it looks like after the conversion to Typst. It's far from complete, though.&lt;/p&gt;
&lt;p&gt;The game was originally meant to be an updated version of my original retroclone, Vox Draconis. I think my first mention of it on this blog was back in April of 2021. Since then, I barely talked about it. I suppose this is as good an opportunity as any to explain what the game is about and what I'm trying to do with it.&lt;/p&gt;
&lt;p&gt;Vox Draconis: Kingdoms of Stone and Fire is a tabletop role-playing game set in a fantasy, pre-iron world filled with dinosaurs, dragons, and warring kingdoms. It's anachronistic and whimsical. While it takes some cues from classic swords-and-sorcery, it's less about fearing the dark and more about carving out a place in the world. It's closer to "noblebright" than it is to "grimdark."&lt;/p&gt;
&lt;p&gt;The rules are meant to be simple but not entirely narrative-driven. Many rules-light games out there, like Powered by the Apocalypse, are intended to just give a framework for a story. Kingdoms of Stone and Fire is meant to power a story, yes, but it's also meant to be an interesting game rules-wise. People who enjoy exploring mechanics and game progression should find it fun, if I design it right.&lt;/p&gt;
&lt;p&gt;I also want to design the game so that it fits into an hour or two of playtime instead of the four or five hours that some games take. This might be the bigger challenge, and I have a lot of thinking to do here. It has to be more than just combat rules that are interesting and worth spending time playing. As it exists now, it's not there.&lt;/p&gt;
&lt;p&gt;Finally, I want the game rules to be relatively short. Too many role-playing games have manuals that are hundreds of pages long. Kingdoms should be short enough that it can be understood and started in half an hour, but have enough content that it can be explored for months or years, if desired. That's also going to be difficult to pull off. I suspect that the crux will be making the main rulebook short and concise, and providing the rest via supplemental works.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Tue, 05 Aug 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/08/revisiting-the-kingdoms-of-stone-and-fire/</guid></item><item><title>p-fast trie: lexically ordered hash map</title><link>https://dotat.at/@/2025-08-04-p-fast-trie.html</link><description>&lt;p&gt;Here’s a sketch of an idea that might or might not be a good idea.
Dunno if it’s similar to something already described in the literature
– if you know of something, please let me know via the links in the
footer!&lt;/p&gt;
&lt;p&gt;The gist is to throw away the tree and interior pointers from a
qp-trie. Instead, the p-fast trie is stored using a hash map organized
into stratified levels, where each level corresponds to a prefix of
the key.&lt;/p&gt;
&lt;p&gt;Exact-match lookups are normal O(1) hash map lookups. Predecessor /
successor searches use binary chop on the length of the key. Where a
qp-trie search is O(k), where k is the length of the key, a p-fast
trie search is O(log k).&lt;/p&gt;
&lt;p&gt;This smaller O(log k) bound is why I call it a “p-fast trie” by
analogy with the x-fast trie, which has O(log log N) query time. (The
“p” is for popcount.) I’m not sure if this asymptotic improvement is
likely to be effective in practice; see my thoughts towards the end of
this note.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-04-p-fast-trie.html#layout" name="layout"&gt;layout&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A p-fast trie consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Leaf objects, each of which has a name.&lt;/p&gt;
&lt;p&gt;Each leaf object refers to its successor forming a circular linked
list. (The last leaf refers to the first.)&lt;/p&gt;
&lt;p&gt;Multiple interior nodes refer to each leaf object.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A hash map containing every (strict) prefix of every name in the
trie. Each prefix maps to a unique interior node.&lt;/p&gt;
&lt;p&gt;Names are treated as bit strings split into chunks of (say) 6 bits,
and prefixes are whole numbers of chunks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An interior node contains a (1&amp;lt;&amp;lt;6) == 64 wide bitmap with a bit
set for each chunk where prefix+chunk matches a key.&lt;/p&gt;
&lt;p&gt;Following the bitmap is a popcount-compressed array of references
to the leaf objects that are the closest predecessor of the
corresponding prefix+chunk key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prefixes are strictly shorter than names so that we can avoid having
to represent non-values after the end of a name, and so that it’s OK
if one name is a prefix of another.&lt;/p&gt;
&lt;p&gt;The size of chunks and bitmaps might change; 6 is a guess that I
expect will work OK. For restricted alphabets you can use something
like my &lt;a href="https://dotat.at/@/2020-07-07-a-compelling-idea-the-genesis-of-my-dns-trie.html"&gt;DNS trie name preparation&lt;/a&gt; trick to squash 8-bit
chunks into sub-64-wide bitmaps.&lt;/p&gt;
&lt;p&gt;In Rust where cross-references are problematic, there might have to be a
hash map that owns the leaf objects, so that the p-fast trie can refer
to them by name. Or use a pool allocator and refer to leaf objects by
numerical index.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-04-p-fast-trie.html#search" name="search"&gt;search&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To search, start by splitting the query string at its end into
prefix + final chunk of bits. Look up the prefix in the hash map and
check the chunk’s bit in the bitmap. If it’s set, you can return the
corresponding leaf object because it’s either an exact match or the
nearest predecessor.&lt;/p&gt;
&lt;p&gt;If it isn’t found, and you want the predecessor or successor, continue
with a binary chop on the length of the query string.&lt;/p&gt;
&lt;p&gt;Look up the chopped prefix in the hash map. The next chunk is the
chunk of bits in the query string immediately after the prefix.&lt;/p&gt;
&lt;p&gt;If the prefix is present and the next chunk’s bit is set, remember the
chunk’s leaf pointer, make the prefix longer, and try again.&lt;/p&gt;
&lt;p&gt;If the prefix is present and the next chunk’s bit is not set and
there’s a lesser bit that is set, return the leaf pointer for the
lesser bit. Otherwise make the prefix shorter and try again.&lt;/p&gt;
&lt;p&gt;If the prefix isn’t present, make the prefix shorter and try again.&lt;/p&gt;
&lt;p&gt;When the binary chop bottoms out, return the longest-matching leaf you
remembered.&lt;/p&gt;
&lt;p&gt;The leaf’s key and successor bracket the query string.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-04-p-fast-trie.html#modify" name="modify"&gt;modify&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When inserting a name, all its prefixes must be added to the hash map
from longest to shortest. At the point where it finds that the prefix
already exists, the insertion routine needs to walk down the
(implicit) tree of successor keys, updating pointers that refer to the
new leaf’s predecessor so they refer to the new leaf instead.&lt;/p&gt;
&lt;p&gt;Similarly, when deleting a name, remove every prefix from longest to
shortest from the hash map where they only refer to this leaf. At the
point where the prefix has sibling nodes, walk down the (implicit)
tree of successor keys, updating pointers that refer to the deleted
leaf so they refer to its predecessor instead.&lt;/p&gt;
&lt;p&gt;I can’t “just” use a concurrent hash map and expect these algorithms
to be thread-safe, because they require multiple changes to the
hashmaps. I wonder if the search routine can detect when the hash map
is modified underneath it and retry.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-08-04-p-fast-trie.html#thoughts" name="thoughts"&gt;thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It isn’t obvious how a p-fast trie might compare to a qp-trie in
practice.&lt;/p&gt;
&lt;p&gt;A p-fast trie will use a lot more memory than a qp-trie because it
requires far more interior nodes. They need to exist so that the
random-access binary chop knows whether to shorten or lengthen the
prefix.&lt;/p&gt;
&lt;p&gt;To avoid wasting space the hash map keys should refer to names in leaf
objects, instead of making lots of copies. This is probably tricky to
get right.&lt;/p&gt;
&lt;p&gt;In a qp-trie the costly part of the lookup is less than O(k) because
non-branching interior nodes are omitted. How does that compare to
a p-fast trie’s O(log k)?&lt;/p&gt;
&lt;p&gt;Exact matches in a p-fast trie are just a hash map lookup. If they are
worth optimizing then a qp-trie could also be augmented with a hash map.&lt;/p&gt;
&lt;p&gt;Many steps of a qp-trie search are checking short prefixes of the key
near the root of the tree, which should be well cached. By contrast, a
p-fast trie search will typically skip short prefixes and instead
bounce around longer prefixes, which suggests its cache behaviour
won’t be so friendly.&lt;/p&gt;
&lt;p&gt;A qp-trie predecessor/successor search requires two traversals, one to
find the common prefix of the key and another to find the prefix’s
predecessor/successor. A p-fast trie requires only one.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Mon, 04 Aug 2025 23:52:21 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-08-04-p-fast-trie.html</guid></item><item><title>IaCConf 2025: Terraform in Depth QA and Fireside Chat</title><link>https://blog.tedivm.com/appearances/2025/08/iacconf-2025-terraform-in-depth-qa-and-fireside-chat/</link><description>At DevOpsDays Chicago this year I found myself answering a lot of questions in random hallway talks, which inspired me to have an entire session at IaCConf this year devoted to answering questions from the audience. These questions and more are answered in my talk, but if you have your own questions not touched on...</description><author>tedious ramblings</author><pubDate>Fri, 01 Aug 2025 17:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/appearances/2025/08/iacconf-2025-terraform-in-depth-qa-and-fireside-chat/</guid></item><item><title>Stack traces for Postgres errors with backtrace_functions</title><link>http://notes.eatonphil.com/2025-07-31-stack-traces-postgres-errors-backtracefunctions.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/stack-traces-postgres-errors-backtracefunctions"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 31 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-07-31-stack-traces-postgres-errors-backtracefunctions.html</guid></item><item><title>IaCConf 2025: Automate All the Things Panel Discussion</title><link>https://blog.tedivm.com/appearances/2025/07/iacconf-2025-automate-all-the-things-panel-discussion/</link><description>I was lucky enough to participate in this panel at IaCConf on AI and ML in the world of Infrastructure as Code. In the spirit of using AI tools to make my life easier, here's an AI generated summary of the panel. This panel discussion explores the impact of AI and ML on infrastructure as...</description><author>tedious ramblings</author><pubDate>Wed, 30 Jul 2025 17:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/appearances/2025/07/iacconf-2025-automate-all-the-things-panel-discussion/</guid></item><item><title>Lamport's Byzantine Generals algorithm in Python</title><link>https://bytepawn.com/lamport-byzantine-generals.html</link><description>&lt;p&gt;I discuss Lamport's Byzantine Generals problem and why it requires a total of N≥3M+1 nodes with M malicious nodes, and then implement the solution using Python with HTTP Flask servers.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-byzantine-generals-2.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Wed, 30 Jul 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/lamport-byzantine-generals.html</guid></item><item><title>Punch Line review</title><link>https://burakku.com/blog/punch-line-review/</link><description>&lt;p&gt;&lt;img alt="Punch Line" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;A punch line without the set-up.&lt;/p&gt;
&lt;p&gt;It’s frankly amazing how little effort PQube has put into this release while asking 49.99€ for it. The Steam version is shipped broken. As soon as you launch the game from the launcher, Steam will mark Punch Line as not running and thus will not update your gameplay time, won’t show you the Steam Overlay, and most importantly, will not allow you to use Steam Input. Seeing as I was trying to play this with my DualSense controller, not having my controller working at all was quite the issue.&lt;/p&gt;
&lt;p&gt;Thankfully there is a workaround for this: open the game installation directory, remove the &lt;code&gt;MAGESgamelauncher.exe&lt;/code&gt; executable, make a copy of the &lt;code&gt;Game.exe&lt;/code&gt; executable and rename that copy to &lt;code&gt;MAGESgamelauncher.exe&lt;/code&gt;. Congratulations, you can now use the Steam overlay, Steam Input and you will be correctly marked as playing Punch Line on your profile.&lt;/p&gt;
&lt;p&gt;I’m guessing that this issue stems from the launcher launching the game executable as a completely separate process instead of a child process, so Steam never hooks the overlay/Steam Input into the &lt;code&gt;Game.exe&lt;/code&gt; process and thus breaks everything. I’m also guessing that PQube has absolutely no quality assurance process since this should be the thing you notice immediately. Punch Line also &lt;em&gt;used&lt;/em&gt; to have achievements on Steam – except those achievements never worked, so they “fixed” the issue by just removing them entirely. And they have the gall to ask 49.99€ for this release.&lt;/p&gt;
&lt;p&gt;&lt;img alt="This sucks!" src="sucks.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Once you’ve managed to actually get the game working, what you have is not that great. Ostensibly Punch Line is a visual novel with puzzle elements, but the puzzle elements in the game are so poor, annoying and unrewarding that the game would be better as a straight visual novel. There’s not really any way to figure out the puzzles, so you’ll just end up taking wild guesses and then doing a reset if you guessed wrong. There isn’t even any kind of fun interactions for non-progressing choices like there is for example in the Somnium segments in Uchikoshi’s AI – The Somnium Files games. The game is also around 12 hours long, and the annoying puzzle segments stop appearing around eight hours in – really for the game’s benefit.&lt;/p&gt;
&lt;p&gt;Besides the obvious launcher issue, there are a bunch of smaller issues that pop up while playing through the game. While it never prevented me from finishing the game, the default controller layout seems to be broken (at least on a DualSense controller), with buttons that should do something not doing it. The background audio sometimes eclipses dialogue audio, even with the default volume settings. And perhaps the most annoying issue: unskippable anime-style opening and ending themes that you will have to watch about twenty times each. It’s possible that these smaller issues could be fixed by tweaking the settings, but I don’t want to spend all of my time fixing this game on PQube’s behalf.&lt;/p&gt;
&lt;p&gt;Visually the game looks about okay – the character visuals are nice, but the environmental details leave room for improvement and the video segments, despite this having a tie-in anime, are kinda bad. I think they’ve had to retime the animations for the dialogue and it ends up looking more awkward than I would’ve imagined. At least during the final hour of the game, which is almost entirely video, the quality is at least decent. Thankfully there are no issues to point out on the music and voice acting side. Well, beyond the volume mixing issue noted earlier that is.&lt;/p&gt;
&lt;p&gt;&lt;img alt="If you see panties twice in a row, seven billion people will die." src="panties.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Really the only highlight of Punch Line is the story. It’s definitely not Uchikoshi’s finest work, but there’s a fairly interesting story with likeable characters. A lot more lighthearted than a lot Uchikoshi works too, considering how much of it revolves around panties. The writing also does a good job of resolving all of the mysteries that it sets up, so you won’t be left with nagging questions at the end.&lt;/p&gt;
&lt;p&gt;I actually really liked the Punch Line anime when I watched it over a decade ago, and it made me want to play through the game. But since it took several years for the game to actually come out in the west, I kinda forgot about the whole thing and only got to it now that the anime’s already over ten years old. Perhaps that was for the best, since I’d already forgotten a lot of the details about it, and most of the story is the exact same as in the anime. Really what the game has going for it over the anime is that there’s a different (and better) ending, and the story is more expanded, as the game has about double the runtime.&lt;/p&gt;
&lt;p&gt;But alas, it’s really hard to recommend going through the story through the game rather than by watching the anime. Punch Line is a good story hidden in a bad game. There’s just way too many issues with this game and they will never ever be resolved. If you absolutely insist on playing the game instead of watching the anime, buy this on a deep discount. PQube simply does not deserve the full price of admission for this mess.&lt;/p&gt;</description><author>ブラック</author><pubDate>Tue, 29 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/punch-line-review/</guid></item><item><title>IaCConf 2025: Taste Testing Tofu</title><link>https://blog.tedivm.com/appearances/2025/07/iacconf-2025-taste-testing-tofu/</link><description>As someone who’s spent years diving into the intricacies of Terraform and OpenTofu, I’ve seen the chaos that comes with “yolo deploying” (rushing changes without verification, only to face outages, compatibility snags, or unexpected bugs). My recent presentation at the first ever Infrastructure as Code Conference highlights a critical shift every team should embrace: treating testing as a non-negotiable cornerstone of infrastructure development.</description><author>tedious ramblings</author><pubDate>Mon, 28 Jul 2025 20:33:30 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/appearances/2025/07/iacconf-2025-taste-testing-tofu/</guid></item><item><title>Getting the KIM-1 to talk to my Mac</title><link>http://blog.jgc.org/2025/02/getting-kim-1-to-talk-to-my-mac.html</link><description>&lt;p&gt;I've written before about &lt;a href="https://blog.jgc.org/2023/11/my-1976-kim-1.html"&gt;my 1976 KIM-1&lt;/a&gt; and &lt;a href="https://blog.jgc.org/2013/04/how-i-coded-in-1985.html"&gt;code I wrote&lt;/a&gt; for a similar one long ago. But I hadn't done much with the KIM-1 and strongly believe that old hardware need to be living machines not still lives. But using the KIM-1 directly (via its keypad and little hexadecimal display) is painful. I wanted to be able to use it with a terminal.&lt;/p&gt;&lt;p&gt;Luckily, the KIM-1 was designed to interface to a terminal: a &lt;a href="https://en.wikipedia.org/wiki/Teletype_Model_33"&gt;Model 33 ASR Teletype&lt;/a&gt;. Teletypes use a type of interface which is unusual to most people (unless you work with industrial machinery where it's pretty common). That interface is a &lt;a href="https://en.wikipedia.org/wiki/Digital_current_loop_interface"&gt;current loop&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;A current loop uses current (often 20mA) rather than voltage (such as the voltage levels seen in common serial interfaces). Prior to the creation of &lt;a href="https://en.wikipedia.org/wiki/RS-232"&gt;RS-232&lt;/a&gt;, current loops were very common. Current loops are still used in industrial settings partly because they have good noise immunity, can go for long distances, and are simple and reliable.&amp;nbsp;&lt;/p&gt;&lt;p&gt;In the KIM-1 manual there's a picture of the way its current loop works when connected to a Teletype:&amp;nbsp;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNI4PoUrRp6VTMXV5sTj-cbd5KxPR0J4kMurosS_YawM1EPLVcKsq-4MIVitMa48NSS9nPgfYU2eui9t6UcwwHXSjzVwrYUIVmGWC1Ex7jFBgjyKnJ0sY7WpVmCbloaGjL5zeRAwX6XtS4l_lHwH88ihpgZ-awjCfDIcFdgCvBJ4MZ-b3pQydWNg/s1368/kim-1-1.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNI4PoUrRp6VTMXV5sTj-cbd5KxPR0J4kMurosS_YawM1EPLVcKsq-4MIVitMa48NSS9nPgfYU2eui9t6UcwwHXSjzVwrYUIVmGWC1Ex7jFBgjyKnJ0sY7WpVmCbloaGjL5zeRAwX6XtS4l_lHwH88ihpgZ-awjCfDIcFdgCvBJ4MZ-b3pQydWNg/w640-h550/kim-1-1.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;When the KIM-1 wants to send data to the Teletype it uses pins S and U of one of its edge connectors. S is permanently connected to +5V via a 150 ohm resistor. When the KIM-1 receives data from the Teletype's keyboard it uses pins R and T. Just like S, R is permanently connected to +5V. The important thing to note is that the KIM-1 is responsible for the current: the Teletype is entirely "dumb". This will matter later in this blog post.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I happened to have lying around &lt;a href="https://www.horter-shop.de/de/rs232-tty-adapter-passiv/151-bausatz-rs232-tty-adapter-passiv-4260404260127.html"&gt;a kit to make an adapter between RS-232 and the Siemens SIMATIC S5 current loop&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAeSbQlVy8BJolG0uDvmgGgtdDsoDFLV6Qo0sHrg-iVI8P0stXBdUg_HsRJqAAIToLVMsjMQu0EQeDfhja17tH9se1YtZXs3yccjPKcwmIUILqzB0I9xizoo8U7jLx_JXb8q2uenEPW7Tc_hv3sGrj7G9CRuep8WmkpyU13Beigo4jbMFZxiAjZw/s982/kim-1-2.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="616" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAeSbQlVy8BJolG0uDvmgGgtdDsoDFLV6Qo0sHrg-iVI8P0stXBdUg_HsRJqAAIToLVMsjMQu0EQeDfhja17tH9se1YtZXs3yccjPKcwmIUILqzB0I9xizoo8U7jLx_JXb8q2uenEPW7Tc_hv3sGrj7G9CRuep8WmkpyU13Beigo4jbMFZxiAjZw/w640-h616/kim-1-2.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;A simple kit to build. Since USB to RS-232 adapters are easy to find I'd be able to communicate with the KIM-1 using it. But first check the circuit diagram of this adapter. There's a small problem on the receive side.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCYmSOPwceRgzMBw50FJPAOYpZdhpJp0Yh3WhN120WAo6qEHLwzn2a5CwFTXAYINa_36dofqGtjI7RXdbVWbcVenGRU4f7f6l-Mhb7lc0DEYEc6aicS7EFNqWhg6ynje8sxQcqZQVyWujO1ARaJUotW1M8gijQh0X27xcgnN3y32U5FNllgFWX7A/s1758/kim-1-3.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCYmSOPwceRgzMBw50FJPAOYpZdhpJp0Yh3WhN120WAo6qEHLwzn2a5CwFTXAYINa_36dofqGtjI7RXdbVWbcVenGRU4f7f6l-Mhb7lc0DEYEc6aicS7EFNqWhg6ynje8sxQcqZQVyWujO1ARaJUotW1M8gijQh0X27xcgnN3y32U5FNllgFWX7A/w640-h318/kim-1-3.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;As is pretty common with current loop applications, optoisolators are used. OK1 handles when the device connected to the RS-232 transmits something. And you can see from the diagram, it expects to be getting 5V, 20mA in on its pin 5 and then out again on pin 4. So, that'll work perfectly for the "keyboard" connection to the KIM-1 (pins R and T).&amp;nbsp;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;But the receive side is a different story. It's handled by OK2 and it's expecting 5V, 20mA on pin 1, but pin 2 goes to GND. So, there's no "loop" back to the device. This won't work with the KIM-1 without a small modification.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I built the kit and and tested it against the KIM-1 like this:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghrUvvLUgSr3RZhod4XiIyxt8viOVH-NQwoLikQMDsZM6jf17czwBXfMYjJKhiB0DeTZMp8ryHVitx-8g1TolmqxAhxfE633YHYpoGBidPl1hcs3eBGgcPeGpE1VULmDIXNxOPGk9D5eiRK7TfxMcWc7uvnXLm3Ht7pP5gBBIq9Ms93bhuvCgyDg/s1280/kim-1-4.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="372" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghrUvvLUgSr3RZhod4XiIyxt8viOVH-NQwoLikQMDsZM6jf17czwBXfMYjJKhiB0DeTZMp8ryHVitx-8g1TolmqxAhxfE633YHYpoGBidPl1hcs3eBGgcPeGpE1VULmDIXNxOPGk9D5eiRK7TfxMcWc7uvnXLm3Ht7pP5gBBIq9Ms93bhuvCgyDg/w640-h372/kim-1-4.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Looks messy but it validated the little modification I needed to make and that I could talk to the KIM-1. Here it is talking at 300 baud using the RS-232 to USB adapter seen above.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQxkZf-cbzjvBVaBvQGWO3XeLjzf6k7kzf0uM0sczMbzcLYzSoZ6sUXsg47ey0zq4VautycgRmuAz1MVwBIbrS01NywuIKJZSoBvvnj7T8kEP8kzpBBPmwndpaBqp7JpibNfBRYRGlHQHh0MShzorcPoQYeN541YcXG3gK__dTB5kvdR6lw3bnUg/s1146/kim-1.gif" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="570" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQxkZf-cbzjvBVaBvQGWO3XeLjzf6k7kzf0uM0sczMbzcLYzSoZ6sUXsg47ey0zq4VautycgRmuAz1MVwBIbrS01NywuIKJZSoBvvnj7T8kEP8kzpBBPmwndpaBqp7JpibNfBRYRGlHQHh0MShzorcPoQYeN541YcXG3gK__dTB5kvdR6lw3bnUg/w640-h570/kim-1.gif" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;So, what's the modification? Well, pin 2 of OK2 goes to ground and we don't want it to, we want it to go the KIM-1's pin U. There are two ways to do this. I could have cut the track on the back of the board that goes from pin 2 to ground, and soldered a wire on the back.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixwnVKqFHl5uFIx6voI_JNpHEdW1WRpV8-zv4w6QydXJ0CTPSLced0JGZgZe8rucLGJG_AqZJzDk2V-7iC1kYzi7o0i-OmKObeNXGq_CjBaSxcbPX0VlK_eGEznspzc6nZ-btpSZdkyL9NZ-6lChzPpcZCz-rEFODbiN5WSQT-jCdVHbTbizarkQ/s540/kim-1-5.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"&gt;&lt;img border="0" height="289" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixwnVKqFHl5uFIx6voI_JNpHEdW1WRpV8-zv4w6QydXJ0CTPSLced0JGZgZe8rucLGJG_AqZJzDk2V-7iC1kYzi7o0i-OmKObeNXGq_CjBaSxcbPX0VlK_eGEznspzc6nZ-btpSZdkyL9NZ-6lChzPpcZCz-rEFODbiN5WSQT-jCdVHbTbizarkQ/s320/kim-1-5.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;But this kit came with all the chips socketed so I took an even simpler route. Bend pin 2 away from the socket and solder directly to it.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdUEHXkzdm5jFklLlLDNbtbLnPCKx7RELxbJMLj2-0lGW7nFmcOD_qzOp8UReQ7tyMTTbaHtjA6_NlwUd0O4Cr48_2T60ZVRf8CGEpAfcjv8qECa7aFXoOFy5kLvMUBa3ZOE_y8ng7kDFry6PALL4aB2aTxRl9kDjWP9ubNAC_l4cs3rrUBAtEvQ/s3193/kim-1-6.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="540" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdUEHXkzdm5jFklLlLDNbtbLnPCKx7RELxbJMLj2-0lGW7nFmcOD_qzOp8UReQ7tyMTTbaHtjA6_NlwUd0O4Cr48_2T60ZVRf8CGEpAfcjv8qECa7aFXoOFy5kLvMUBa3ZOE_y8ng7kDFry6PALL4aB2aTxRl9kDjWP9ubNAC_l4cs3rrUBAtEvQ/w640-h540/kim-1-6.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;And that worked fine. The kit also came with a nice little box and so the final adapter looks neat.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfUeBT17s3TjORhTssUtiUGLaW1M_CpBMrWIwnNG737fVgehQnvGZ9-9VoHNNFOeDzNCtUrjQwO82_4VMayPlsyQhOwI23jMH-1rPcHCzkVWi9ZkJe8T1zICJAcAjRnyJaqNyr8ZZj7IRLZn3ivPDXWUQFV5VkuNaKU1H-cSDk07jFKqq6it_PxQ/s1280/kim-1-7.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfUeBT17s3TjORhTssUtiUGLaW1M_CpBMrWIwnNG737fVgehQnvGZ9-9VoHNNFOeDzNCtUrjQwO82_4VMayPlsyQhOwI23jMH-1rPcHCzkVWi9ZkJe8T1zICJAcAjRnyJaqNyr8ZZj7IRLZn3ivPDXWUQFV5VkuNaKU1H-cSDk07jFKqq6it_PxQ/w640-h480/kim-1-7.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The grey wire goes to the KIM-1, the white wire is a USB-A cable that I'm using to power the adapter and the KIM-1 itself.&amp;nbsp;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRNc94lv50bDs6CSJnYk8rEeC9wAiQlf_tIUCAgHw203ey4NJwxBF8MyhroSH1TyB6FPqsk4i4VTQGUbQwYcXBGvlCv5fI6P0qOYMcE5sB2t3VDm-ebZY-iMjZgWndXe2Gs5_bY4cHcwNNUpXKMBr8GXj-C_dkNU7oDPRYYKoqrgdfJQOBb1nDTw/s1280/kim-1-8.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRNc94lv50bDs6CSJnYk8rEeC9wAiQlf_tIUCAgHw203ey4NJwxBF8MyhroSH1TyB6FPqsk4i4VTQGUbQwYcXBGvlCv5fI6P0qOYMcE5sB2t3VDm-ebZY-iMjZgWndXe2Gs5_bY4cHcwNNUpXKMBr8GXj-C_dkNU7oDPRYYKoqrgdfJQOBb1nDTw/w640-h480/kim-1-8.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The KIM-1 will happily communicate as 1200 baud with 7 bit ASCII and 2 stop bits. I'm using minicom to communicate with it. The main thing is to set the delete key to send DEL instead of BS since the KIM-1 needs to receive a DEL on start up so that it can measure the Mac's transmission speed.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;One of the Teletype functions available is to load a program from punched tape. I don't have an actual Teletype but I can emulate the punched tape format and that'll allow me to upload programs easily.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9IJ_wZJFKEaDD5UhZ_cb3FiMO9IHgOfrMnrKOQjYdMuSvZNwO7cxND1M774qpg639z71zcWxWUPrvc-YFYLWIS5SdfzlZ0nMFZpaUKHE5JANoIwmHLXEa6NDE-oAkgwPyukU3RtHTmdhisdCeYCaD_SgRDAldBPtge1gbSAbM8XLe-hQEccRrqg/s1208/kim-1-9.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9IJ_wZJFKEaDD5UhZ_cb3FiMO9IHgOfrMnrKOQjYdMuSvZNwO7cxND1M774qpg639z71zcWxWUPrvc-YFYLWIS5SdfzlZ0nMFZpaUKHE5JANoIwmHLXEa6NDE-oAkgwPyukU3RtHTmdhisdCeYCaD_SgRDAldBPtge1gbSAbM8XLe-hQEccRrqg/w538-h640/kim-1-9.png" width="538" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;The format itself is not complicated.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWxEHqPySDq6ch5c9XLYO8NOf7B8x-ExUa0UDgFltgwhqYWDBAyNrgpLmn3EWcnBeWPOcQ6TNOcZOz0ia3TM2MFcvx5ol54r9ENRTb76a_S-jd78mbyl1J85011K7a4wTRgYyTDaPfsOEjdAP-Py9ny5sk-oRxHawHil31r6Wba_QiU8QKcw8etg/s1058/kim-1-10.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWxEHqPySDq6ch5c9XLYO8NOf7B8x-ExUa0UDgFltgwhqYWDBAyNrgpLmn3EWcnBeWPOcQ6TNOcZOz0ia3TM2MFcvx5ol54r9ENRTb76a_S-jd78mbyl1J85011K7a4wTRgYyTDaPfsOEjdAP-Py9ny5sk-oRxHawHil31r6Wba_QiU8QKcw8etg/w614-h640/kim-1-10.png" width="614" /&gt;&lt;/a&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 28 Jul 2025 17:28:23 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/02/getting-kim-1-to-talk-to-my-mac.html</guid></item><item><title>What Is A Good Programmer?</title><link>https://thecodist.com/what-is-a-good-programmer/</link><description>&lt;p&gt;Am I a good programmer? The short answer is: I don&amp;#x2019;t know what that means.&lt;/p&gt;&lt;p&gt;I have been programming for 52 years now, having started in a public high school class in 1973, which is pretty rare because few high schools offered such an opportunity back then. I&lt;/p&gt;</description><author>The Codist</author><pubDate>Sun, 27 Jul 2025 18:11:00 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/what-is-a-good-programmer/</guid></item><item><title>Becoming a High Taste Tester</title><link>https://www.swyx.io/high-taste-tester</link><description>&lt;p&gt;&lt;em&gt;theres a specific reason why i'm writing this post i can't disclose yet. but am sharing my prep work in public&lt;/em&gt;&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Fri, 25 Jul 2025 11:03:40 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/high-taste-tester</guid></item><item><title>Performance lessons of implementing lbzcat in Rust</title><link>https://anisse.astier.eu/lbzip2-rs.html</link><description>&lt;p&gt;&lt;em&gt;This was originally published as a &lt;a href="https://social.treehouse.systems/@Aissen/114909194418695763"&gt;thread on Mastodon&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For fun I implemented lbzip2's &lt;a href="https://github.com/anisse/lbzip2-rs/"&gt;lbzcat (parallel bzip2 decompression) clone in rust&lt;/a&gt;, using the bzip2 crate. &lt;/p&gt;
&lt;h1&gt;Baseline: bzip2 vs bzip2-rs&lt;/h1&gt;
&lt;p&gt;First, let's look at the baseline. The &lt;a href="https://fosstodon.org/@trifectatech"&gt;Trifecta Tech Foundation&lt;/a&gt; recently &lt;a href="https://trifectatech.org/blog/bzip2-crate-switches-from-c-to-rust/"&gt;claimed that bzip2-rs had faster decompression than libbzip2's original C …&lt;/a&gt;&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Thu, 24 Jul 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/lbzip2-rs.html</guid></item><item><title>Ode to the anti-Mac: Panasonic Toughbook FZ-40</title><link>http://blog.jgc.org/2025/07/ode-to-anti-mac-panasonic-toughbook-fz.html</link><description>&lt;p&gt;I own two computers that I use regularly. The first is my main machine, a rather old &lt;a href="https://blog.jgc.org/2024/01/my-daily-driver-is-older-than-i-thought.html"&gt;Apple MacBook Pro&lt;/a&gt;, the other is a Panasonic Toughbook FZ-40. They are polar opposites. Where the Mac is designed with tightly coupled software and hardware and is all smooth curves and delicate design, the Toughbook is designed to be used wearing gloves, outside and in rough situations that would damage the Mac.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Its built-in handle is a sign that this thing is heavy and robust.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR5ksuSkhfiuU-4f5KaWpjAKREr207HVQ5bNvVXix9HUqLg9Ft97l3zDSXfL6e6tu3NLbhk9DN7tgC391cXwRE3RDLdco6FXjUNvWUrkIfbtqI0SHfMKM6ANwitSnM3Kkpr315x9T4GmYoG8k4wsAdv3Fb1mRIswxkKstSMqw_F5QoyqfDMs8DUQ/s1280/tb-1.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="355" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR5ksuSkhfiuU-4f5KaWpjAKREr207HVQ5bNvVXix9HUqLg9Ft97l3zDSXfL6e6tu3NLbhk9DN7tgC391cXwRE3RDLdco6FXjUNvWUrkIfbtqI0SHfMKM6ANwitSnM3Kkpr315x9T4GmYoG8k4wsAdv3Fb1mRIswxkKstSMqw_F5QoyqfDMs8DUQ/w400-h355/tb-1.jpeg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;And where the MacBook has a feeble port selection, my Toughbook has VGA, Serial, two Ethernet sockets, HDMI, a micro SD card, SIM card, USB-A (SS5 and SS10), head and microphone jack, Thunderbolt and USB-C, and built in GPS. There's also a smart card reader.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It feels like it's designed to do stuff where connecting to the real world is involved.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The camera has a physical cover and all the ports are protected by locking doors.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-NlKla7coIopu5v4R42QD-eootEmOl2ximpHxs1al31cHNu9a0XuQWNISbD7Ge4Lu8EWBjd1-ndjuljMoSz0RMTM8l19K4p26gKHY0rbDlYqDkl4Y2h-eU8-GL9vkA0Db1jMYAqLVBFnKaMuEv6EzJlVR252Il7AxkmpGN2TwrwsYw8jnC2pk8g/s5712/tb-2.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-NlKla7coIopu5v4R42QD-eootEmOl2ximpHxs1al31cHNu9a0XuQWNISbD7Ge4Lu8EWBjd1-ndjuljMoSz0RMTM8l19K4p26gKHY0rbDlYqDkl4Y2h-eU8-GL9vkA0Db1jMYAqLVBFnKaMuEv6EzJlVR252Il7AxkmpGN2TwrwsYw8jnC2pk8g/w480-h640/tb-2.jpeg" width="480" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;There are also some special Panasonic buttons (P1 through P4). P2 drops you into "Concealed Mode" which instantly cuts all the light coming from the machine.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE34EIE8i2r_zZ80SeR8HHQuwLq8EKTCHbWWkVpEPLqxqBcxh8cDpgithqnBj24zaED14PiKfZOk0QPVwNS9HdgaYf0Li2xFaiOMTm6-w3lKoGs-uCM8aDze-X7NLb0DXmCPDQndP4NnCD2WPmyAosSp2PzTAKks4BCENQRTrpNvKTpO3L_815nw/s1707/tb-3.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE34EIE8i2r_zZ80SeR8HHQuwLq8EKTCHbWWkVpEPLqxqBcxh8cDpgithqnBj24zaED14PiKfZOk0QPVwNS9HdgaYf0Li2xFaiOMTm6-w3lKoGs-uCM8aDze-X7NLb0DXmCPDQndP4NnCD2WPmyAosSp2PzTAKks4BCENQRTrpNvKTpO3L_815nw/w480-h640/tb-3.jpeg" width="480" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;P3 drops you into "Night Mode" for all those moments where your submarine is being hunted by the Soviets and you need to preserve your night vision to be able to see the instruments ready. Sadly there's no "&lt;a href="https://www.youtube.com/watch?v=jr0JaXfKj68"&gt;one ping&lt;/a&gt;" option.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRoMryzChFHB6f8UVBQsAUMWrdCsddZb_acVrXDObFLh9DtkQC1JrgJPTj3R26zqPdSTVT_G1q_UL7t6HBggG_cLdXx_hwgoI3r2lEl4YdcROEG0suCiKDC7lIA1kdps2INWXR81IupVZyM448PW8MNzCsnpkZMQEo5nqwsAb16t4Ty2rDuqVemA/s1707/tb-4.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRoMryzChFHB6f8UVBQsAUMWrdCsddZb_acVrXDObFLh9DtkQC1JrgJPTj3R26zqPdSTVT_G1q_UL7t6HBggG_cLdXx_hwgoI3r2lEl4YdcROEG0suCiKDC7lIA1kdps2INWXR81IupVZyM448PW8MNzCsnpkZMQEo5nqwsAb16t4Ty2rDuqVemA/w480-h640/tb-4.jpeg" width="480" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;The machine runs Windows 11 (which I don't love) but a lot of industrial software is only available on Windows (along with things like the LEGO MINDSTORMS software which was weak on the Mac and full-featured on Windows).&amp;nbsp;&lt;/p&gt;&lt;p&gt;I may be the only person in the world programming LEGO on a Toughbook with the option to drop into Night Mode during, uh, LEGO emergencies. But, hey, &lt;a href="https://behind-the-screens.tv/#ironman"&gt;LEGO worked for Iron Man&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;And then there's the red&amp;nbsp;&lt;a href="https://groups.google.com/g/toughbook/c/J__ssDTCm_0"&gt;F11&lt;/a&gt;&amp;nbsp;key with a raised bump around it to prevent accidental activation.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 21 Jul 2025 14:25:57 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/07/ode-to-anti-mac-panasonic-toughbook-fz.html</guid></item><item><title>I Don't Like Imports</title><link>https://kevincox.ca/2025/07/20/no-imports/</link><description>&lt;p style="margin: 0; padding: 0;"&gt;I think this is an unusual opinion, so I thought I would share.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;I prefer not to import external symbols into the local scope. For example, I prefer:&lt;/p&gt;&lt;pre class="_1"&gt;&lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;let&lt;/span&gt; &lt;span class="_4"&gt;body&lt;/span&gt; &lt;span class="_3"&gt;=&lt;/span&gt; &lt;span class="_5"&gt;reqwest&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;get&lt;/span&gt;(&lt;span class="_6"&gt;&lt;span class="_6"&gt;"&lt;/span&gt;https://kevincox.ca/feed.atom&lt;span class="_6"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;span class="_3"&gt;.await?.&lt;/span&gt;&lt;span class="_5"&gt;text&lt;/span&gt;()&lt;span class="_3"&gt;.await?&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;p style="margin: 0; padding: 0;"&gt;over&lt;/p&gt;&lt;pre class="_1"&gt;&lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt; &lt;span class="_5"&gt;reqwest&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;get; &lt;span class="_7"&gt;// At the top of the file.&lt;/span&gt;

&lt;span class="_7"&gt;// Inside some function.&lt;/span&gt;
&lt;span class="_3"&gt;let&lt;/span&gt; &lt;span class="_4"&gt;body&lt;/span&gt; &lt;span class="_3"&gt;=&lt;/span&gt; &lt;span class="_5"&gt;get&lt;/span&gt;(&lt;span class="_6"&gt;&lt;span class="_6"&gt;"&lt;/span&gt;https://kevincox.ca/feed.atom&lt;span class="_6"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;span class="_3"&gt;.await?.&lt;/span&gt;&lt;span class="_5"&gt;text&lt;/span&gt;()&lt;span class="_3"&gt;.await?&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="_8" id="unambiguous" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#unambiguous" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Unambiguous&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;There are various reasons for this, but all the most important ones boil down to being unambiguous. When I am reading code I see &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;library&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Foo&lt;/span&gt;&lt;/code&gt; and know that it comes from &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;library&lt;/span&gt;&lt;/code&gt;. I see &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;crate::&lt;/span&gt;&lt;span class="_5"&gt;Foo&lt;/span&gt;&lt;/code&gt; and know it comes from elsewhere in my code and if I see just &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Foo&lt;/span&gt;&lt;/code&gt; I know it is from the current file (and possibly private, so I could be a bit more careful with how I use the API). I don’t need to guess or lookup what &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Foo&lt;/span&gt;&lt;/code&gt; I am working with. (And when I do inevitably guess I don’t need to have that background process asking “did I guess wrong?”)&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;I find when I am working on codebases where the convention is to import things I am very frequently tracking down the definition of some type or function as I don’t know whether it is local or from which library. This is especially painful when reading or reviewing code in a web interface. If you are reading a patch the imports are likely not expanded (if they didn’t change) and even if you have the whole file expanded you need to check if the referenced value is the global import from the top of the file or some local shadow.&lt;/p&gt;&lt;h2 class="_8" id="copy-paste" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#copy-paste" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Copy + Paste&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;Another thing I love about fully qualified names is that I can just copy and paste code around or move it from file to file without needing to figure out which imports are needed in the new file (and inevitably importing the wrong &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Error&lt;/span&gt;&lt;/code&gt; at least a few times). For the most part code in other files and examples online can just be pasted into the file and work.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;It’s not some dramatic productivity boon, but it certainly makes cleanups and refactors much smoother and a frustrating tedious step that is necessary when working with import-heavy code.&lt;/p&gt;&lt;aside class="_9"&gt;&lt;p style="margin: 0; padding: 0;"&gt;This would be a great editor feature. If I am copying from one file to another in the same editor it should be able to also pull in the required imports. Technically it should be fairly straightforward to any editor with enough language services for precise autocomplete. The only real challenge would be coming up with a new name in the case of a identifier conflict.&lt;/p&gt;&lt;/aside&gt;&lt;h2 class="_8" id="long-names" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#long-names" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;But The Names Are So Long!&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;In some cases they are. But more often than not this is just a poor choice by the library author (in my opinion). For example in Rust the standard hash-map is &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;collections&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;HashMap&lt;/span&gt;&lt;/code&gt;. But even that is just an alias. If you want to work on an &lt;a class="_a" href="https://doc.rust-lang.org/stable/std/collections/hash_map/enum.Entry.html"&gt;&lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Entry&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; you are now typing &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;collections&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;hash_map&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Entry&lt;/span&gt;&lt;/code&gt;. I see no reason it couldn’t have been &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;HashMap&lt;/span&gt;&lt;/code&gt; and &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;HashMapEntry&lt;/span&gt;&lt;/code&gt; or &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;HashMap&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Entry&lt;/span&gt;&lt;/code&gt;. (I don’t mind a small amount of nesting when it is very strongly associated, but still don’t really see much point.)&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;I much prefer C++‘s style where almost everything is just under &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;std&lt;/span&gt;&lt;/code&gt;. For example &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_4"&gt;unordered_map&lt;/span&gt;&lt;/code&gt;. An iterator is slightly nested at &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;unordered_map&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_4"&gt;iterator&lt;/span&gt;&lt;/code&gt; but if you ask me that is still a pretty reasonable name. And I would much rather see that in code than wonder what &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;iterator&lt;/span&gt;&lt;/code&gt; we are working with.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;If it hurts stop hitting yourself, don’t find a way to numb the pain.&lt;/p&gt;&lt;h2 class="_8" id="conflicts" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#conflicts" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Conflicts&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;I see this come up as an argument against, and I am pretty dumbfounded. Some people see that you have &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;io&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Error&lt;/span&gt;&lt;/code&gt; and &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;fmt&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Error&lt;/span&gt;&lt;/code&gt;. So they can’t both be &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Error&lt;/span&gt;&lt;/code&gt;. But that is kind of my point! When I see code referencing &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Error&lt;/span&gt;&lt;/code&gt; I don’t know which of error type from the dozens libraries that the code might be using it is! That is what makes the &lt;code class="_2 language-rust"&gt;{&lt;span class="_4"&gt;library&lt;/span&gt;}&lt;span class="_3"&gt;::&lt;/span&gt;{&lt;span class="_4"&gt;name&lt;/span&gt;}&lt;/code&gt; pattern so great, it is conflict free. If I use that throughout my code I never have to worry if &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Write&lt;/span&gt;&lt;/code&gt; is &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;fmt&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Write&lt;/span&gt;&lt;/code&gt;, &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;io&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Write&lt;/span&gt;&lt;/code&gt; or &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;crate::&lt;/span&gt;&lt;span class="_5"&gt;Write&lt;/span&gt;&lt;/code&gt; because it is spelt out! I would much rather the types be actually named &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;FmtWrite&lt;/span&gt;&lt;/code&gt; and have everyone “agree” on the name (because they have no choice) than half the time having it called &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Write&lt;/span&gt;&lt;/code&gt; and half the time having it called &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;FmtWrite&lt;/span&gt;&lt;/code&gt;, &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;WriteFmt&lt;/span&gt;&lt;/code&gt; or &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;DebugWrite&lt;/span&gt;&lt;/code&gt; (because some other &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Write&lt;/span&gt;&lt;/code&gt; was in scope and the programmer needed to come up with their own name).&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;This isn’t an imagined problem. I regularly find myself reading &lt;a class="_a" href="https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html"&gt;rustdoc&lt;/a&gt; documentation and needing to hover over a type name so that my browser displays the fully qualified path in the link target! I feel that which &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Error&lt;/span&gt;&lt;/code&gt; or which &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Write&lt;/span&gt;&lt;/code&gt; is being used is important enough that it be immediately visible, in both code and generated documentation.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;Avoiding imports means that the library authors are responsible for solving conflicts. And we are back to the fact that when I see a type or function it is unambiguous and I don’t have to waste brain cycles figuring out what &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Error&lt;/span&gt;&lt;/code&gt; I am looking at.&lt;/p&gt;&lt;h2 class="_8" id="exceptions" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#exceptions" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;But Sometimes They Are &lt;strong style="margin: 0; padding: 0;"&gt;Really&lt;/strong&gt; Long&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;I’m not a stickler to this rule. There are some cases where I will use imports. Sometimes the names are just too long and repetitive. In that case I strongly prefer a local &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt;&lt;/code&gt; with a short scope. This ensures that I don’t need to look far to resolve the ambiguity.&lt;/p&gt;&lt;pre class="_1"&gt;&lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt; &lt;span class="_5"&gt;sentry&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;integrations&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;tracing&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;EventFilter&lt;/span&gt;;
&lt;span class="_3"&gt;match&lt;/span&gt; &lt;span class="_3"&gt;*&lt;/span&gt;&lt;span class="_4"&gt;meta&lt;/span&gt;&lt;span class="_3"&gt;.&lt;/span&gt;&lt;span class="_5"&gt;level&lt;/span&gt;() {
	&lt;span class="_5"&gt;tracing&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Level&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_b"&gt;ERROR&lt;/span&gt; &lt;span class="_3"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="_5"&gt;EventFilter&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Event&lt;/span&gt;,
	&lt;span class="_5"&gt;tracing&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Level&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_b"&gt;WARN&lt;/span&gt; &lt;span class="_3"&gt;=&amp;gt;&lt;/span&gt; {
		&lt;span class="_3"&gt;if&lt;/span&gt; &lt;span class="_4"&gt;module&lt;/span&gt;&lt;span class="_3"&gt;.&lt;/span&gt;&lt;span class="_5"&gt;starts_with&lt;/span&gt;(&lt;span class="_6"&gt;&lt;span class="_6"&gt;"&lt;/span&gt;hickory_proto::&lt;span class="_6"&gt;"&lt;/span&gt;&lt;/span&gt;) {
			&lt;span class="_5"&gt;EventFilter&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Ignore&lt;/span&gt;
		} &lt;span class="_3"&gt;else&lt;/span&gt; {
			&lt;span class="_5"&gt;EventFilter&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Event&lt;/span&gt;
		}
	}
	&lt;span class="_5"&gt;tracing&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Level&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_b"&gt;INFO&lt;/span&gt; &lt;span class="_3"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="_5"&gt;EventFilter&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Ignore&lt;/span&gt;,
	&lt;span class="_5"&gt;tracing&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Level&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_b"&gt;DEBUG&lt;/span&gt; &lt;span class="_3"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="_5"&gt;EventFilter&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Ignore&lt;/span&gt;,
	&lt;span class="_5"&gt;tracing&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Level&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_b"&gt;TRACE&lt;/span&gt; &lt;span class="_3"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="_5"&gt;EventFilter&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Ignore&lt;/span&gt;,
}
&lt;/code&gt;&lt;/pre&gt;&lt;p style="margin: 0; padding: 0;"&gt;(And more often than not when I run into this case it isn’t because the name is intrinsically high-entropy, but because someone got carried away with hierarchy. &lt;em style="margin: 0; padding: 0;"&gt;Stop hitting yourself.&lt;/em&gt;)&lt;/p&gt;&lt;h2 class="_8" id="rust" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Rust-Specific Notes&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;I’ve been using Rust as an example but been trying to avoid being Rust-specific. But it is my primary language, so I do have some thoughts to share. If you don’t care about Rust you can stop reading now.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;Avoiding imports in Rust is going against the grain. I would recommend that all crates &lt;a class="_a" href="https://kevincox.ca/2025/07/20/no-imports/#rust-flat-api"&gt;provide a flat API&lt;/a&gt;. However, for many projects avoiding &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt;&lt;/code&gt; isn’t the right choice. Just follow the common coding convention and be happy. To see a real-world example of both a flat API and avoiding &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt;&lt;/code&gt; you can check out &lt;a class="_a" href="https://gitlab.com/kevincox/rl-core"&gt;rl-core&lt;/a&gt; which is a small project where I am quite glad I chose to break convention. A lot of my “for-me” or “internal” code is written in a similar style.&lt;/p&gt;&lt;h3 class="_8" id="rust-flat-api" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust-flat-api" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Provide a Flat API&lt;/a&gt;&lt;/h3&gt;&lt;p style="margin: 0; padding: 0;"&gt;By default, Rust wants to ship your file structure as your exports (&lt;a class="_a" href="https://en.wikipedia.org/wiki/Conway%27s_law"&gt;Conway’s law&lt;/a&gt;). So for example &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;mylib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Foo&lt;/span&gt;&lt;/code&gt; needs to be defined in &lt;code class="_2 language-text"&gt;src/lib.rs&lt;/code&gt; and &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;mylib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;submodule&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Bar&lt;/span&gt;&lt;/code&gt; needs to be defined in &lt;code class="_2 language-text"&gt;src/submodule.rs&lt;/code&gt; or &lt;code class="_2 language-text"&gt;src/submodule/mod.rs&lt;/code&gt;.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;With a single re-export per module this can be solved!&lt;/p&gt;&lt;pre class="_1"&gt;&lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;mod&lt;/span&gt; &lt;span class="_5"&gt;config&lt;/span&gt;; &lt;span class="_3"&gt;pub&lt;/span&gt; &lt;span class="_3"&gt;use&lt;/span&gt; &lt;span class="_5"&gt;config&lt;/span&gt;&lt;span class="_3"&gt;::*&lt;/span&gt;;
&lt;span class="_3"&gt;mod&lt;/span&gt; &lt;span class="_5"&gt;tracker&lt;/span&gt;; &lt;span class="_3"&gt;pub&lt;/span&gt; &lt;span class="_3"&gt;use&lt;/span&gt; &lt;span class="_5"&gt;tracker&lt;/span&gt;&lt;span class="_3"&gt;::*&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;p style="margin: 0; padding: 0;"&gt;One extra statement to free you from hierarchy. Re-exports are well-supported by Rust so rustdoc and the compiler will pick &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;mylib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Config&lt;/span&gt;&lt;/code&gt; as the path to show users rather than &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;mylib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;config&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Config&lt;/span&gt;&lt;/code&gt;. (I don’t know the exact heuristics but when working like this there will be a single public export, so there isn’t much cleverness needed.)&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;&lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;pub&lt;/span&gt; &lt;span class="_3"&gt;use&lt;/span&gt;&lt;/code&gt; also only sets the &lt;strong style="margin: 0; padding: 0;"&gt;maximum&lt;/strong&gt; export visibility. This means that &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;pub&lt;/span&gt;&lt;/code&gt;, &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;pub&lt;/span&gt;(&lt;span class="_3"&gt;crate&lt;/span&gt;)&lt;/code&gt; and private things in the module will have the expected visibility. So the definitions in the files look the same as they normally would and the export lines only need to be added when you add or remove modules.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The only real downside of this approach is the &lt;code class="_2 language-text"&gt;src/lib.rs&lt;/code&gt; will see everything as local and can reference &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;Config&lt;/span&gt;&lt;/code&gt; rather than &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;crate::&lt;/span&gt;&lt;span class="_5"&gt;Config&lt;/span&gt;&lt;/code&gt;. But this is a very minor issue and the solution is not to put anything into &lt;code class="_2 language-text"&gt;lib.rs&lt;/code&gt;. (Keeping only module definitions and re-exports in &lt;code class="_2 language-text"&gt;lib.rs&lt;/code&gt; is a very common pattern anyways.)&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The nice thing about just re-exporting everything is that it also frees you from deciding where to put things based on what you want the exported name to be. You don’t need to put &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;collections&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;hash_map&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;HashMap&lt;/span&gt;&lt;/code&gt; and &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;collections&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;hash_map&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Entry&lt;/span&gt;&lt;/code&gt; in the same file to have them appear at the same place, you can put them into separate files if you wish or the same file if you need access to non-public APIs. (The Rust &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;std&lt;/span&gt;&lt;/code&gt; implementation does actually re-export these from various places, re-exports are very common and not something to be feared.)&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;Personally I just put every single type and most functions into separate files. I find that it keeps everything simple and organized and I prefer switching between different files than different parts of a file when going back and forth. So for example in rl-core I define &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;rl_core&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Config&lt;/span&gt;&lt;/code&gt; in &lt;code class="_2 language-text"&gt;src/config.rs&lt;/code&gt; and &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;rl_core&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Tracker&lt;/span&gt;&lt;/code&gt; in &lt;code class="_2 language-text"&gt;lib/tracker.rs&lt;/code&gt;.&lt;/p&gt;&lt;h3 class="_8" id="rust-traits" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust-traits" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Traits&lt;/a&gt;&lt;/h3&gt;&lt;p style="margin: 0; padding: 0;"&gt;Traits are where things start to get hairy. To call a trait method as a method the trait needs to be in scope. So if you want to call &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;foo&lt;/span&gt;&lt;span class="_3"&gt;.&lt;/span&gt;&lt;span class="_5"&gt;a_method&lt;/span&gt;()&lt;/code&gt; where &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;a_method&lt;/span&gt;&lt;/code&gt; is &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;somelib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;TheType&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_4"&gt;a_method&lt;/span&gt;&lt;/code&gt; you need to &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt; &lt;span class="_5"&gt;somelib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;TheType&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;However you always have the option to call the method using a fully-qualified path. For example &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;somelib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;TheType&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;a_method&lt;/span&gt;(&lt;span class="_3"&gt;&amp;amp;&lt;/span&gt;&lt;span class="_4"&gt;foo&lt;/span&gt;)&lt;/code&gt; or &lt;code class="_2 language-rust"&gt;&amp;lt;&lt;span class="_5"&gt;Foo&lt;/span&gt; &lt;span class="_3"&gt;as&lt;/span&gt; &lt;span class="_5"&gt;somelib&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;TheType&lt;/span&gt;&amp;gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;static_func&lt;/span&gt;()&lt;/code&gt;. For the occasional method call I often do this because I do like seeing where &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;.&lt;/span&gt;&lt;span class="_5"&gt;a_method&lt;/span&gt;()&lt;/code&gt; comes from, it isn’t always clear if it is a method directly on &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;foo&lt;/span&gt;&lt;/code&gt; or which trait defined it. However nesting a few calls with this quickly becomes messy and sometimes I make a practical choice to just use the &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt;&lt;/code&gt;. I prefer to do this just for a short scope to help keep the origin information close to the code, but for very common traits I will sometimes add a global import like &lt;code class="_2 language-rust"&gt;&lt;span class="_3"&gt;use&lt;/span&gt; &lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;io&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;Write&lt;/span&gt; &lt;span class="_3"&gt;as&lt;/span&gt; _&lt;/code&gt; (make the trait methods available but don’t actually pull the name into scope).&lt;/p&gt;&lt;h3 class="_8" id="rust-std" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust-std" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;&lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;std&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p style="margin: 0; padding: 0;"&gt;The elephant in the room is that in Rust &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;std&lt;/span&gt;&lt;/code&gt; (and &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;core&lt;/span&gt;&lt;/code&gt;) love &lt;em style="margin: 0; padding: 0;"&gt;love&lt;/em&gt; &lt;strong style="margin: 0; padding: 0;"&gt;love&lt;/strong&gt; hierarchy and lots of names are super long. I wish I could have a long talk with whoever decided it was really important that &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_5"&gt;sync&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_4"&gt;atomic&lt;/span&gt;&lt;/code&gt; really needed to be nested under &lt;code class="_2 language-rust"&gt;&lt;span class="_4"&gt;sync&lt;/span&gt;&lt;/code&gt;. I think we would have been fine to reserve the top level &lt;code class="_2 language-rust"&gt;&lt;span class="_5"&gt;std&lt;/span&gt;&lt;span class="_3"&gt;::&lt;/span&gt;&lt;span class="_4"&gt;atomic&lt;/span&gt;&lt;/code&gt; for these operations even though they are synchronization related.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;It is what it is. I’m not going to advocate for The Great std Naming Reform even if I would prefer it. We have a pattern, and we can deal with it. Most of the names aren’t so long that they need to be imported so most of the time I just spell them out. Occasionally I find myself repeating a long name frequently and just import it. I have yet to be stricken by a bolt of lightning from the heavens.&lt;/p&gt;</description><author>Kevin Cox's Blog</author><pubDate>Mon, 21 Jul 2025 01:15:00 GMT</pubDate><guid isPermaLink="true">https://kevincox.ca/2025/07/20/no-imports/</guid></item><item><title>Pimping my Casio: Part Deux</title><link>http://blog.jgc.org/2025/07/pimping-my-casio-part-deux.html</link><description>&lt;p&gt;Close to three years ago I wrote about using Oddly Specific Objects' alternate "motherboard" to modify a classic Casio F-91W watch:&amp;nbsp;&lt;a href="https://blog.jgc.org/2022/10/pimping-my-casio-with-oddly-specific.html"&gt;Pimping my Casio with Oddly Specific Objects' alternate motherboard and firmware.&lt;/a&gt;&amp;nbsp;That blog post goes into the detail of swapping out the guts of the Casio and building and uploading the firmware.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Happily, Oddly Specific Objects is back with a "Pro" version of their alternative Casio internals which now features an accelerometer and an alternative LCD. The original &lt;a href="https://www.sensorwatch.net"&gt;Sensor Watch&lt;/a&gt; used the existing Casio LCD display but the custom LCD allows for more complicated text to be displayed on screen. It's not a dot-matrix display so there are lots of limitations but it's still a fun upgrade.&lt;/p&gt;&lt;p&gt;Also, the Sensor Watch Pro, as it's called, requires no soldering (unlike the original version). Here's one I prepared earlier (that is a Casio F-91W with new internals):&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1iVxRuZ4xaxGibS6JwRe8xEO0eKwOZ2dlDaA7b-eJh6hAX4H5wkQyGRS117Sy5H2Z66BAKfZ5iAtlWUSZJQjIIdusaiSo-IsQ1qSO1ksLiWKWD_QAsWbOi6egCNo30qVTqjtE-aivfl6sFJEZ9XXB1XrkgrA7nKRmBCmU4RkpzCRMJgDUWC7aCw/s2246/sw-pro-1.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1iVxRuZ4xaxGibS6JwRe8xEO0eKwOZ2dlDaA7b-eJh6hAX4H5wkQyGRS117Sy5H2Z66BAKfZ5iAtlWUSZJQjIIdusaiSo-IsQ1qSO1ksLiWKWD_QAsWbOi6egCNo30qVTqjtE-aivfl6sFJEZ9XXB1XrkgrA7nKRmBCmU4RkpzCRMJgDUWC7aCw/w414-h640/sw-pro-1.jpeg" width="414" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;You can see the new display in action there showing the day as FRI. As with the original Sensor Watch there's a browser-based emulator that uses &lt;a href="https://emscripten.org"&gt;emscripten&lt;/a&gt; to get the watch running on your computer. This is pretty important because flashing new firmware to the watch requires dismantling it.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSB8nh3LmqIhogyf8_oCkSlsXhM2DkDSdJZZ_WqstKKlT0CjrFSUUje7k6jZZuw5WgWfmE_VvJ8oGvjaafFPOcr9NH33aqCWNJ023SvvYVvgCshM8lysDMRDK54lrL7fyq7RrtYmNNvR1syDmGswtEYDZY-oCY_tEZZ3Xs8vHtyw1ZWBwBs00qYw/s2038/sw-pro-2.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="464" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSB8nh3LmqIhogyf8_oCkSlsXhM2DkDSdJZZ_WqstKKlT0CjrFSUUje7k6jZZuw5WgWfmE_VvJ8oGvjaafFPOcr9NH33aqCWNJ023SvvYVvgCshM8lysDMRDK54lrL7fyq7RrtYmNNvR1syDmGswtEYDZY-oCY_tEZZ3Xs8vHtyw1ZWBwBs00qYw/w640-h464/sw-pro-2.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;Since there's no soldering the process of upgrading your Casio is simply take it apart and put back in the parts that came in the kit (along with a small piece of metal that's transferred from the original Casio watch to make the battery connection):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPPyoeQqJm4O8uQDLilWonpEhGj1-Hb5DvQ6yk5xYWjJ_V7cKrwq3tjKFuh-F3IvekcmfCCIMGyhc9-2iaV6VWtAkOLjuZ96Xh0RcWT7t4E3CSOa-XAo8FE4k6MgvG89JTEk1aW794Q94YutuHsnL0QXawxjmi7evePaExUvTM36UTFyafQPMT4w/s1280/sw-pro3.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="596" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPPyoeQqJm4O8uQDLilWonpEhGj1-Hb5DvQ6yk5xYWjJ_V7cKrwq3tjKFuh-F3IvekcmfCCIMGyhc9-2iaV6VWtAkOLjuZ96Xh0RcWT7t4E3CSOa-XAo8FE4k6MgvG89JTEk1aW794Q94YutuHsnL0QXawxjmi7evePaExUvTM36UTFyafQPMT4w/w640-h596/sw-pro3.jpeg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;Since I bought the optional accelerometer add on it's necessary to put in place a little piece of Kapton tape (you can see if covering the five pads on the image above) and the insert the accelerometer board itself:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvSDKLm6K0X7zvjAgopLSk3mVf_VAzIsoGCejMyRyxprXEhZ_Ate4vYEPNYesDlteWI2acDnD8F4ldYJmyv3v9arkJY2Uea-qpplAo8QoVJuwe-sKb-mOZr7jMmIHQNq7igMN6aOrS1kReahxCF6XE1Al-RgG81aoANogqf2-fuBzue_PLh_nVAw/s1280/sw-pro-4.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="624" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvSDKLm6K0X7zvjAgopLSk3mVf_VAzIsoGCejMyRyxprXEhZ_Ate4vYEPNYesDlteWI2acDnD8F4ldYJmyv3v9arkJY2Uea-qpplAo8QoVJuwe-sKb-mOZr7jMmIHQNq7igMN6aOrS1kReahxCF6XE1Al-RgG81aoANogqf2-fuBzue_PLh_nVAw/w640-h624/sw-pro-4.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;The five vertical traces at the top are the micro USB interface used for flashing the firmware.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidQSoy_thbpiUEwWkH9tY6Go4rSFFCwwxj0VU4xOPD6DnHKrCTf18qHOg-3vF1iJiMm2kpkYpznSmvnsj0ZuubvdcdADtaHlwquCrjrrF-fSv8LVSFSZX_F5KA8VtMSsBiv7b5PghdDiG0kippfxJEESiidFm8hn9JvU21ERPXRrCQwy8j954nVA/s1280/sw-pro-5.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="442" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidQSoy_thbpiUEwWkH9tY6Go4rSFFCwwxj0VU4xOPD6DnHKrCTf18qHOg-3vF1iJiMm2kpkYpznSmvnsj0ZuubvdcdADtaHlwquCrjrrF-fSv8LVSFSZX_F5KA8VtMSsBiv7b5PghdDiG0kippfxJEESiidFm8hn9JvU21ERPXRrCQwy8j954nVA/w640-h442/sw-pro-5.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;I modified the firmware to remove Imperial units and 12 hour clock (which I never want) and to choose &lt;a href="https://github.com/joeycastillo/second-movement/commit/d5a1544d88963d607bc85826437c4a317cf56834"&gt;my own&amp;nbsp;&lt;/a&gt;&lt;span style="color: #0000ee;"&gt;&lt;span&gt;&lt;u&gt;selection&lt;/u&gt;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;of screens&amp;nbsp;(I added the tally, accelerometer and light sensor screens).&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;The complete sequence of commands needed to build the firmware was:&lt;br /&gt;&lt;span style="font-family: courier;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;span style="font-family: courier;"&gt;brew install --cask gcc-arm-embedded&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;git clone https://github.com/joeycastillo/second-movement&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;cd second-movement&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;git submodule update --init --recursive&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;make BOARD=sensorwatch_pro DISPLAY=custom&lt;/span&gt;&lt;/div&gt;&lt;p style="text-align: left;"&gt;And then to upload to the watch you connect the micro USB, click the tiny switch on the back twice very rapidly and a volume called &lt;span style="font-family: courier;"&gt;WATCHBOOT&lt;/span&gt; appears on your machine. You can copy the firmware over to the watch with &lt;span style="font-family: courier;"&gt;make install&lt;/span&gt;.&amp;nbsp;&lt;/p&gt;&lt;p style="text-align: left;"&gt;To run the emulator you build with emscripten:&lt;/p&gt;&lt;div style="text-align: left;"&gt;&lt;span style="font-family: courier;"&gt;brew install emscripten&lt;br /&gt;emmake make&amp;nbsp;BOARD=sensorwatch_pro DISPLAY=custom&lt;br /&gt;python3 -m http.server -d build-sim&lt;/span&gt;&lt;/div&gt;&lt;p style="text-align: left;"&gt;And then visit localhost:8000/firmware.html in your browser.&lt;/p&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Sat, 19 Jul 2025 16:05:59 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/07/pimping-my-casio-part-deux.html</guid></item><item><title>Migrated gemlog content into this site</title><link>https://benovermyer.com/blog/2025/07/migrated-gemlog-content-into-this-site/</link><description>&lt;p&gt;I'm in the process of sunsetting my gemlog. I liked the idea of the Gemini protocol, but it never really grew beyond a specific set of niche topics. Further, it's just easier to write in Markdown, which is what I use for writing this site.&lt;/p&gt;
&lt;p&gt;As such, I've migrated all of my gemlog posts into this site.&lt;/p&gt;
&lt;p&gt;Here's a list of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2025/07/dune-awakening-first-impressions/"&gt;Dune: Awakening - First Impressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2025/04/awaiting-dune/"&gt;Awaiting Dune&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2025/02/old-and-new-experiences/"&gt;Old and New Experiences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/12/first-day-in-dragonflight/"&gt;First Day in Dragonflight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/11/return-to-blizzard/"&gt;Return to Blizzard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/05/finished-shadowbringers/"&gt;Finished Shadowbringers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/04/no-mans-sky/"&gt;No Man's Sky&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Ben Overmyer's Site</author><pubDate>Thu, 17 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/07/migrated-gemlog-content-into-this-site/</guid></item><item><title>Agentic Coding Adoption Cost Cycle</title><link>https://smcleod.net/2025/07/agentic-coding-adoption-cost-cycle/</link><description>The two common themes I see with engineers adopting agentic coding tools</description><author>smcleod.net</author><pubDate>Tue, 15 Jul 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/07/agentic-coding-adoption-cost-cycle/</guid></item><item><title>Megadimension Neptunia VII review</title><link>https://burakku.com/blog/megadimension-neptunia-vii-review/</link><description>&lt;p&gt;&lt;img alt="Megadimension Neptunia VII" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Not another Victory.&lt;/p&gt;
&lt;p&gt;When I started playing through Megadimension Neptunia VII, I thought that this was Neptunia finally reaching a level of effort and polish never before seen in the franchise. Character sprites were improved, there were levels that weren’t just recycled from the previous games, enemies were voiced and so on. Unfortunately, despite the seeming jump in quality, I started noticing a problem: the game wasn’t fun to play.&lt;/p&gt;
&lt;p&gt;You know how in RPG games the game decides to take away your party members for plot reasons, leaving your party understaffed and making those characters underleveled when they rejoin your party? And how that is annoying as all hell? Well, Compile Heart decided that this annoying mechanic was worth making into an entire game. It took like 17 hours for my party to be at full fighting capacity, and they don’t even stick around after that. You really don’t want to get attached to any single party member because the chances are that they’re going to be missing for most of the story.&lt;/p&gt;
&lt;p&gt;It also feels like they’ve completely forgotten about basic game design that previous titles in the Neptunia franchise understood. Things like having save points before a boss fight. Oh, you thought to challenge the boss after only 25 minutes of grinding? Time to lose your 25 minutes worth of progression. There’s also a point where you can get your character’s health too low for a boss fight, with no possibility to heal. Hopefully you didn’t overwrite your previous save, since you’ll have to reload at that point too. Compile Heart really has no considerations towards the value of your time in this game.&lt;/p&gt;
&lt;p&gt;The overworld traversal is also a great example of just wasting your time. Movement between dungeons, cities and other points of interest involve moving on a gameboard-like map where every single space has a chance to randomly pull you into combat. Trying to get from one overworld plot event to another might require you to fight three random groups of enemies. And as you progress through the game, your party level far outstrips the enemies, requiring you to just waste time beating small fries for no good reason. Even if you don’t have to fight a single mob, the gameboard-like movement is just slow to watch. There is a fast-forward button but what the game needs is a skip button. And to further add insult to injury, you’re also expected to spend credits to build the paths to dungeons and other cities. Could I just pay a bit extra and build a path that isn’t infested with low-level mobs?&lt;/p&gt;
&lt;p&gt;I think Compile Heart even realises how awful many of these things are since they allow disabling a lot of them once you reach New Game+. Personally, I’d design my game in such a way that you don’t want to disable a dozen "features" after a single playthrough.&lt;/p&gt;
&lt;p&gt;VII might also have the worst achievements in franchise history. Card Master is an awful waste of time that will give you carpal tunnel, headache and a disdain for life itself. Treasure Hunter, while not reaching similar peak awfulness, is also a giant time sink without really any fun aspects to it. If you decide to challenge these, hopefully you have a second monitor to watch YouTube videos on as you painfully grind through them all. Completionists beware.&lt;/p&gt;
&lt;p&gt;While there’s some good and interesting changes to the combat, like having a combo system that discourages just spamming the same attack again and again, combat feels worse than for example in the Rebirth titles. The timing for symbol attacks, trying to align your attacks to hit multiple enemies, having to chase after enemies dispersing all over the place. Everything just feels that much worse. Then there’s things like boss fights where the boss is immune to damage until you hit him enough times to break his damage immunity item – a process that takes way too long to be remotely fun. It’s not even a hard fight since the boss only does chip damage, meaning that at no point are you actually in danger.&lt;/p&gt;
&lt;p&gt;As for the story, it’s just okay. Definitely not as good as Victory’s but it’s a serviceable Neptunia story, at least towards the end. I’m not really a fan of how the game is split into three different parts. It doesn’t really even add much to the story since it’s all continuous anyway. The conditions to reach the true ending are also annoying to fulfil and probably better saved for the New Game+ when the game stops doing its most annoying features.&lt;/p&gt;
&lt;p&gt;The technical side of VII is nothing short of a train wreck. The sound settings are awful, you can’t have controller and keyboard input working at the same time, the game is capped at 60 FPS and 1920×1080 resolution and the battle load times are bad considering I have an SSD and the game isn’t that graphically intensive. I also had issues with the controller button mapping with my DualSense but maybe that was the fault of Steam Input. Hasn’t happened in other games though. And to put a cherry on top of everything, using my KVM switch (which unplugs the keyboard and mouse to use them with another PC) makes the game crash.&lt;/p&gt;
&lt;p&gt;Thankfully there is a mod that allows you to run the game at such arbitrary resolutions as 2560×1440, and mercifully above 60 FPS. While my PC is quite powerful, I saw no negative effects from running this mod and would recommend it to anyone playing on hardware more advanced than a Steam Deck. And since the mod works so wonderfully, it really makes me wonder why Compile Heart couldn’t implement these features themselves. Although I think I know the answer: Compile Heart is a two-bit kusoge developer that wants PC gamer money for the least possible effort.&lt;/p&gt;
&lt;p&gt;What a shame that the sequel to the best Neptunia game, Victory, and the only mainline Neptunia game we’ve gotten in the last decade, is such an awful game. And this is by Neptunia standards, which have historically not been that high anyway. I don't know why they decided that VII stands for "Victory II" with a quality gap like this. Usually with these things you can at least recommend the game to fans of the series, but I don’t think you can really even recommend it to them. The game just isn’t fun to play and the story’s not interesting enough to justify it. If you insist on having to experience the game for yourself, at least do yourself a favour and grab the game on a sale. Maybe that way you’ll feel less bad about playing it.&lt;/p&gt;</description><author>ブラック</author><pubDate>Tue, 15 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/megadimension-neptunia-vii-review/</guid></item><item><title>Lamport's logical clocks and the Ricart–Agrawala Distributed Mutual Exclusion algorithm in Python</title><link>https://bytepawn.com/lamport-logical-clocks-distributed-mutual-exclusion.html</link><description>&lt;p&gt;This post shows how to implement the Ricart–Agrawala Distributed Mutual Exclusion algorithm with Lamport's logical clocks using HTTP Flask servers.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-dme.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 13 Jul 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/lamport-logical-clocks-distributed-mutual-exclusion.html</guid></item><item><title>Exploring Lamport's Bakery algorithm in Python</title><link>https://bytepawn.com/lamport-bakery-python.html</link><description>&lt;p&gt;This post shows how to implement Lamport’s Bakery algorithm with Python HTTP Flask servers. The Python code is short and readable, helping newcomers grasp the algorithm’s logic without the syntactic overhead of C++. The asynchronous HTTP flow also generates plenty of overlap between workers, so the need for mutual exclusion is obvious. Exposing the variables through REST endpoints enables observation of ticket picking and critical section entry in real time, potentially making the flow of the algorithm easier to understand.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-bakery-python.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 11 Jul 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/lamport-bakery-python.html</guid></item><item><title>Programming ebooks by Sundeep Agarwal</title><link>https://learnbyexample.github.io/books/</link><description>&lt;p&gt;This post lists my programming ebooks with details like PDF/EPUB purchase links, GitHub repos, web versions, testimonials, etc. All my ebooks are self-published. You can get these ebooks individually or as part of bundles. You can also read them online for free.&lt;/p&gt;
&lt;h1 id="bundles-books"&gt;&lt;p style="color: #c05b4d;"&gt;Bundles 📚&lt;/h1&gt;
&lt;p align="center"&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books"&gt;&lt;img alt="All books bundle" src="/images/books/all_books_bundle.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Poster created using &lt;a href="https://www.canva.com/"&gt;Canva&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All books bundle&lt;/strong&gt;: &lt;a href="https://leanpub.com/b/learnbyexample-all-books"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books"&gt;gumroad&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;13 programming ebooks on Regular Expressions, Linux CLI tools, Python, Vim and more&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux CLI Text Processing bundle&lt;/strong&gt;: &lt;a href="https://leanpub.com/b/linux-cli-text-processing"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing"&gt;gumroad&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;GNU grep, sed, awk, Perl and Ruby one-liners, GNU coreutils, CLI computing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Awesome regex&lt;/strong&gt;: &lt;a href="https://leanpub.com/b/regex"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/regex"&gt;gumroad&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Python, Ruby, JavaScript Regular expressions&lt;/li&gt;
&lt;li&gt;GNU grep, ripgrep, GNU sed, GNU awk CLI tools (BRE/ERE, PCRE, Rust regex crate, PCRE2)&lt;/li&gt;
&lt;li&gt;Vim regexp&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Magical one-liners&lt;/strong&gt;: &lt;a href="https://leanpub.com/b/oneliners"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/oneliners"&gt;gumroad&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;GNU grep, ripgrep, GNU sed, GNU awk, Ruby, Perl CLI tools&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn by example Python bundle&lt;/strong&gt;: &lt;a href="https://leanpub.com/b/python-bundle"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/python-bundle"&gt;gumroad&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Python introduction, Regular expressions and Projects&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ruby Text processing&lt;/strong&gt;: &lt;a href="https://leanpub.com/b/ruby-textprocessing"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/ruby-textprocessing"&gt;gumroad&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Ruby regular expressions, Ruby One-Liners Guide&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="testimonials-heart-eyes"&gt;&lt;p style="color: #c05b4d;"&gt;Testimonials 😍&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;I love your books on regex...As a student from the Digital VLSI space, it is indeed useful now and definitely in the future. It's really well written and really easy to understand the examples.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://old.reddit.com/r/Python/comments/i0m2sy/i_know_python_basics_what_next/fzql5gh/"&gt;feedback on reddit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;It's very thorough, written with care, and presented in a way that makes sense. Even as an intermediate Python programmer, I found use in this book.&lt;/p&gt;
&lt;p&gt;— feedback by &lt;a href="https://healeycodes.com/"&gt;Andrew Healey&lt;/a&gt; on &lt;a href="https://news.ycombinator.com/item?id=26082464"&gt;Hacker News&lt;/a&gt; for &amp;quot;100 Page Python Intro&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Step up your cli fu with this fabulous intro &amp;amp; deep dive into awk. I learned a ton of tricks!&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/killchain/status/1246820137455452163"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Your Practice Python Projects book is really helping me to reinforce my knowledge and mastery of Python as I'm learning.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/tayporware/status/1446499855988400129"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;In my opinion the book does a great job of quickly presenting examples of how commands can be used and then paired up to achieve new or interesting ways of manipulating data. Throughout the text there are little highlights offering tips on extra functionality or limitations of certain commands. For instance, when discussing the &lt;em&gt;shuf&lt;/em&gt; command we're warned that &lt;em&gt;shuf&lt;/em&gt; will not work with multiple files. However, we can merge multiple files together (using the &lt;em&gt;cat&lt;/em&gt; command) and then pass them to &lt;em&gt;shuf&lt;/em&gt;. These little gems of wisdom add a dimension to the book and will likely save the reader some time wondering why their scripts are not working as expected.&lt;/p&gt;
&lt;p&gt;— book review by Jesse Smith on &lt;a href="https://distrowatch.com/weekly.php?issue=20211206#book"&gt;distrowatch.com&lt;/a&gt; for &amp;quot;Command line text processing with GNU Coreutils&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Literally was having a mini-breakdown about not understanding Regex in algorithm solutions the other day and now I'm feeling so much better, so thank YOU! I genuinely feel like I'm developing the skill for spotting when and where to use them after so much practice!&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/codingwithlucy/status/1450668315635036160"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This Ruby one-liners cookbook is incredible. Pretty mind boggling all the stuff you can do.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/jbrancha/status/1506766118756786189"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi, great work releasing this! Trying to explain vim concisely is always an interesting challenge and I had a great time reading your attempt in this book. I always find it really interesting on how people try to group certain vim functions in a way that makes sense to people that don't use vim. I think you cover that idea pretty well in your 'Vim philosophy and features' section whilst not making it overly abstract and keeping it relatable.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://news.ycombinator.com/item?id=30684232"&gt;feedback on Hacker News&lt;/a&gt; by doix for &amp;quot;Vim Reference Guide&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I consider myself pretty experienced at shell-fu and capable of doing most things I set out to achieve in either bash scripts or fearless one-liners. However, my awk is rudimentary at best, I think mostly because it's such an unforgiving environment to experiment in. These books you've written are great for a bit of first principles insight and then quickly building up to functional usage. I will have no hesitation in referring colleagues to them!&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://news.ycombinator.com/item?id=31930840"&gt;feedback on Hacker News&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Thank you for choosing to write and share your knowledge. I read your books on CLI and sed - I think they are very comprehensive and very well explained. Keep up the great work&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/le_anh_phuong/status/1628149732760604672"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This is fantastic! 👏 I use Perl one-liners for record and text processing a lot and this will be definitely something I will keep coming back to - I’ve already learned a trick from “Context Matching” (9) 🙂&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://programming.dev/comment/3277968"&gt;feedback on Linux@lemmy.ml&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Nice book! I just started trying to get into linux today and you have some tips I haven’t found elsewhere and the text is an enjoyable read so far.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://old.reddit.com/r/linux4noobs/comments/1adrx6c/linux_guide_for_beginners/kk3dypr/"&gt;feedback on reddit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I discovered your books recently and they’re awesome, thank you! As a 20 year *nix they made me realize how much more there are to these rock solid and ancient tools, once you spend the time to actually learn the intricacies of them.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://old.reddit.com/r/commandline/comments/1byumd6/learn_gnu_coreutils_text_processing_tools_like/l2pk5bd/"&gt;feedback on reddit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I love the whole learn by example premise. Those exercises at the end are so valuable, as it often times leads me to find multiple solutions which helps me conceptualize how commands work with each other much better!&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://old.reddit.com/r/linux4noobs/comments/lkbr65/learn_grep_sed_awk_perl_oneliners_with_hundreds/l6btf13/?context=3"&gt;feedback on reddit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h1 id="100-page-python-intro"&gt;&lt;p style="color: #ff9933;"&gt;100 Page Python Intro&lt;/h1&gt;
&lt;p&gt;Short, introductory guide for the Python programming language, suited for those already familiar with programming basics.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="100 Page Python Intro cover image" src="https://raw.githubusercontent.com/learnbyexample/100_page_python_intro/main/images/py_intro_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/100_page_python_intro/blob/main/sample_chapters/100_page_python_intro_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/100pagepythonintro"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/100pagepythonintro"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/100_page_python_intro/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="understanding-python-re-gex"&gt;&lt;p style="color: #ff9933;"&gt;Understanding Python re(gex)?&lt;/h1&gt;
&lt;p&gt;Learn Python Regular Expressions step-by-step from beginner to advanced levels with 300+ examples. Both &lt;code&gt;re&lt;/code&gt; and &lt;code&gt;regex&lt;/code&gt; modules are covered. Exercises are also included to test your understanding.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Understanding Python re(gex)? cover image" src="https://raw.githubusercontent.com/learnbyexample/py_regular_expressions/master/images/py_regex_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/py_regular_expressions/blob/master/sample_chapters/py_regex_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/py_regex"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/py_regular_expressions/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="practice-python-projects"&gt;&lt;p style="color: #ff9933;"&gt;Practice Python Projects&lt;/h1&gt;
&lt;p&gt;Know Python basics but don't know what to do next? Take the next step in your programming journey with real world inspired Python projects.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Practice Python Projects cover image" src="https://raw.githubusercontent.com/learnbyexample/practice_python_projects/main/images/py_projects_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/practice_python_projects/blob/main/sample_chapters/practice_python_projects_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_projects"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/py_projects"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/practice_python_projects"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/practice_python_projects/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="understanding-javascript-regexp"&gt;&lt;p style="color: #ff9933;"&gt;Understanding JavaScript RegExp&lt;/h1&gt;
&lt;p&gt;Learn JavaScript Regular Expressions step-by-step from beginner to advanced levels with hundreds of examples and exercises.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Understanding JavaScript RegExp cover image" src="https://raw.githubusercontent.com/learnbyexample/learn_js_regexp/master/images/js_regexp_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_js_regexp/blob/master/sample_chapters/js_regexp_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/js_regexp"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/js_regexp"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/learn_js_regexp/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="cli-text-processing-with-gnu-grep-and-ripgrep"&gt;&lt;p style="color: #ff9933;"&gt;CLI text processing with GNU grep and ripgrep&lt;/h1&gt;
&lt;p&gt;Example based guide to mastering GNU grep and ripgrep. Exercises are also included to test your understanding.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="CLI text processing with GNU grep and ripgrep cover image" src="https://raw.githubusercontent.com/learnbyexample/learn_gnugrep_ripgrep/master/images/grep_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep/blob/master/sample_chapters/gnu_grep_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/gnugrep_ripgrep"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/gnugrep_ripgrep"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="cli-text-processing-with-gnu-sed"&gt;&lt;p style="color: #ff9933;"&gt;CLI text processing with GNU sed&lt;/h1&gt;
&lt;p&gt;Example based guide to mastering GNU sed. Exercises are also included to test your understanding.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="CLI text processing with GNU sed cover image" src="https://raw.githubusercontent.com/learnbyexample/learn_gnused/master/images/sed_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnused/blob/master/sample_chapters/gnu_sed_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/gnu_sed"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/gnu_sed"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnused"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/learn_gnused/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="cli-text-processing-with-gnu-awk"&gt;&lt;p style="color: #ff9933;"&gt;CLI text processing with GNU awk&lt;/h1&gt;
&lt;p&gt;Example based guide to mastering GNU awk one-liners. Exercises are also included to test your understanding.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="CLI text processing with GNU awk cover image" src="https://raw.githubusercontent.com/learnbyexample/learn_gnuawk/master/images/gawk_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnuawk/blob/master/sample_chapters/gnu_awk_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/gnu_awk"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/gnu_awk"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/learn_gnuawk/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="understanding-ruby-regexp"&gt;&lt;p style="color: #ff9933;"&gt;Understanding Ruby Regexp&lt;/h1&gt;
&lt;p&gt;Learn Ruby Regular Expressions step-by-step from beginner to advanced levels with hundreds of examples and exercises.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Understanding Ruby Regexp cover image" src="https://raw.githubusercontent.com/learnbyexample/Ruby_Regexp/master/images/ruby_regexp_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/Ruby_Regexp/blob/master/sample_chapters/ruby_regexp_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pay what you want for pdf/epub:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/rubyregexp"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/rubyregexp"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/Ruby_Regexp"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/Ruby_Regexp/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="ruby-one-liners-guide"&gt;&lt;p style="color: #ff9933;"&gt;Ruby One-Liners Guide&lt;/h1&gt;
&lt;p&gt;Example based guide for text processing with Ruby from the command line. Exercises are also included to test your understanding.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Ruby One-Liners Guide cover image" src="https://raw.githubusercontent.com/learnbyexample/learn_ruby_oneliners/master/images/ruby_oneliners_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_ruby_oneliners/blob/master/sample_chapters/ruby_oneliners_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/ruby-oneliners"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/ruby-oneliners"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_ruby_oneliners"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/learn_ruby_oneliners/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="perl-one-liners-guide"&gt;&lt;p style="color: #ff9933;"&gt;Perl One-Liners Guide&lt;/h1&gt;
&lt;p&gt;Example based guide for text processing with Perl from the command line. Exercises are also included to test your understanding.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Perl One-Liners Guide cover image" src="https://raw.githubusercontent.com/learnbyexample/learn_perl_oneliners/main/images/perl_oneliners_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_perl_oneliners/blob/main/sample_chapters/perl_oneliners_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/perl-oneliners"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/perl-oneliners"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/learn_perl_oneliners"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/learn_perl_oneliners/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="cli-text-processing-with-gnu-coreutils"&gt;&lt;p style="color: #ff9933;"&gt;CLI text processing with GNU Coreutils&lt;/h1&gt;
&lt;p&gt;You might be already aware of popular coreutils commands like &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt; and so on. This book will teach you more than twenty of such specialized text processing tools provided by the &lt;code&gt;GNU coreutils&lt;/code&gt; package.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="CLI text processing with GNU Coreutils cover image" src="https://raw.githubusercontent.com/learnbyexample/cli_text_processing_coreutils/main/images/cli_coreutils_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/cli_text_processing_coreutils/blob/main/sample_chapters/cli_text_processing_coreutils_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/cli_coreutils"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/cli_coreutils"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/cli_text_processing_coreutils"&gt;GitHub repo for code snippets and more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/cli_text_processing_coreutils/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="vim-reference-guide"&gt;&lt;p style="color: #ff9933;"&gt;Vim Reference Guide&lt;/h1&gt;
&lt;p&gt;This is intended as a concise learning resource for beginner to intermediate level Vim users. It has more in common with cheatsheets than a typical text book. Topics like Regular Expressions and Macros have more detailed explanations and examples due to their complexity.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Vim Reference Guide cover image" src="https://learnbyexample.github.io/vim_reference/images/vim_reference_guide_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/vim_reference/blob/master/sample_chapters/vim_reference_guide_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/vim_reference_guide"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/vim_reference_guide"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/vim_reference"&gt;GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/vim_reference/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="linux-command-line-computing"&gt;&lt;p style="color: #ff9933;"&gt;Linux Command Line Computing&lt;/h1&gt;
&lt;p&gt;This ebook aims to teach &lt;strong&gt;Linux command line tools and Shell Scripting&lt;/strong&gt; for &lt;strong&gt;beginner to intermediate&lt;/strong&gt; level users. The main focus is towards &lt;strong&gt;managing your files&lt;/strong&gt; and performing &lt;strong&gt;text processing tasks&lt;/strong&gt;. Plenty of &lt;strong&gt;examples&lt;/strong&gt; are provided to make it easier to understand a particular tool and its various features. &lt;strong&gt;Exercises&lt;/strong&gt; at the end of chapters will help you practice what you've learned and &lt;strong&gt;solutions&lt;/strong&gt; are provided for reference. I hope this ebook would make it much easier for you to discover CLI tools, features and learning resources than my own blundering experience.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Linux Command Line Computing cover image" src="https://learnbyexample.github.io/cli-computing/images/cli_computing_ls.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/cli-computing/blob/master/sample_chapters/cli_computing_sample.pdf"&gt;Sample chapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy pdf/epub from:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/cli_computing"&gt;gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/cli_computing"&gt;leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/cli-computing"&gt;GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/cli-computing/"&gt;web version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Feedback: &lt;a href="https://twitter.com/learn_byexample"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Thu, 10 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/books/</guid></item><item><title>Practice Python Projects book announcement</title><link>https://learnbyexample.github.io/practice-python-projects-book-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;My &amp;quot;&lt;strong&gt;Practice Python Projects&lt;/strong&gt;&amp;quot; ebook presents five beginner-to-intermediate level projects inspired by real world use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/practice_python_projects/calculator/calculator.html"&gt;Enhance your CLI experience with a custom Python calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/practice_python_projects/poll_data_analysis/poll_data_analysis.html"&gt;Analyzing poll data from a Reddit comment thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/practice_python_projects/find_typos/find_typos.html"&gt;Finding typos in plain text and Markdown files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/practice_python_projects/mcq/multiple_choice_questions.html"&gt;Creating a GUI for evaluating multiple choice questions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/practice_python_projects/square_tic_tac_toe/square_tic_tac_toe.html"&gt;Square Tic Tac Toe — creating a GUI game with AI logic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To test your understanding and to make it more interesting, you'll also be presented with exercises at the end of each project. Resources for further exploration are also mentioned throughout the book.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="ebook-links"&gt;Ebook links&lt;a class="zola-anchor" href="#ebook-links"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can buy the &lt;strong&gt;PDF/EPUB&lt;/strong&gt; versions of the ebook using these links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_projects"&gt;https://learnbyexample.gumroad.com/l/py_projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/py_projects"&gt;https://leanpub.com/py_projects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also get them as part of the &lt;strong&gt;Learn by example Python bundle&lt;/strong&gt; using these links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/python-bundle"&gt;https://learnbyexample.gumroad.com/l/python-bundle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/python-bundle"&gt;https://leanpub.com/b/python-bundle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;Check out my &lt;a href="https://learnbyexample.github.io/tips/"&gt;programming tips&lt;/a&gt; covering Python, command line tools and Vim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos"&gt;Vim tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Your Practice Python Projects book is really helping me to reinforce my knowledge and mastery of Python as I'm learning.&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/tayporware/status/1446499855988400129"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/practice_python_projects/preface.html"&gt;https://learnbyexample.github.io/practice_python_projects/preface.html&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/practice_python_projects"&gt;https://github.com/learnbyexample/practice_python_projects&lt;/a&gt; for programs, example files, markdown source and other details about the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/practice_python_projects/issues"&gt;https://github.com/learnbyexample/practice_python_projects/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 10 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/practice-python-projects-book-announcement/</guid></item><item><title>Gemini Nano in Chrome 137: notes for AI Engineers</title><link>https://www.swyx.io/gemini-nano</link><description>&lt;p&gt;at long last, Gemini Nano is almost here for all Chrome users (i was originally misinformed that it was in Chrome 138 - but i checked my own facts and since Chrome 137+ it is starting to be &lt;a href="https://www.lifewire.com/chrome-ai-scam-protection-11731694?utm_source=chatgpt.com"&gt;shipped unflagged&lt;/a&gt; in limited situations). I was &lt;a href="https://news.ycombinator.com/item?id=44482710"&gt;reminded by this HN post&lt;/a&gt;. I expect it to be shipped fully unflagged by end of year.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Tue, 08 Jul 2025 06:52:00 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/gemini-nano</guid></item><item><title>Dune: Awakening - First Impressions</title><link>https://benovermyer.com/blog/2025/07/dune-awakening-first-impressions/</link><description>&lt;p&gt;Well, I've been playing Dune: Awakening for about a month now. I joined a guild right away and spent the first few hours after launch exploring and doing early quests ("journeys" in Dune parlance). I spent most of that first day in Discord voice chat with my guild.&lt;/p&gt;
&lt;p&gt;I love the game. I've sunk many hours into it already. As expected, I spend most of my time building interesting things and then going out and collecting materials to build more interesting things. I haven't participated in PVP yet, though my guild (Dementium) has some very dedicated PVP players.&lt;/p&gt;
&lt;p&gt;The crafting system is fun. The gating of crafting certain items behind acquiring particular resources is well done. I would say I wish they had more available for PVE players but it's not that big a deal to be unable to craft the highest-tier items without a guild's support. You don't really need the highest-tier items unless you're participating in the PVP endgame.&lt;/p&gt;
&lt;p&gt;It's still early in the game's life, and that shows. Cheaters are rampant right now, especially in the PVP area, the Deep Desert. I have yet to be personally affected, but it's a little worrying. I have hope that the developers will fix the issue before too long.&lt;/p&gt;
&lt;p&gt;Two days ago I built my first assault ornithopter. She's a big beast! I haven't taken her out for more than an initial spin, but I think that'll change when I need to go gather resources in a hard-to-reach spot.&lt;/p&gt;
&lt;p&gt;That's it for now. We'll see how long Dune keeps my interest, especially with university starting in a couple months.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Tue, 08 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/07/dune-awakening-first-impressions/</guid></item><item><title>notes from Naval (2025)</title><link>https://www.swyx.io/naval-2025</link><description>&lt;p&gt;someone I resonate a lot with is Naval Ravikant - his classic "How to Get Rich Without Getting Lucky" (henceforth HTGR) is formative to a lot of my thinking, including &lt;a href="https://www.swyx.io/marketing-yourself"&gt;How to Market Yourself Without Being A Celebrity&lt;/a&gt; and "Play Long Term Games with Long Term People". HTGR also includes "How to Get Lucky", but I came across that before Naval, from pmarca, and wrote up my mnemonic/insight (that HTGL misses the role of strategy) in &lt;a href="http://swyx.io/create-luck"&gt;How to Create Luck&lt;/a&gt;.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 07 Jul 2025 00:41:21 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/naval-2025</guid></item><item><title>Exploring Lamport's Bakery algorithm in C++20</title><link>https://bytepawn.com/lamport-bakery-cpp.html</link><description>&lt;p&gt;I explore Lamport’s Bakery algorithm and show how a naive C++20 implementation breaks on weak memory models, how to fix it with &lt;code&gt;std::atomic&lt;/code&gt;, and how to implement bounded tickets. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/lamport-bakery.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 06 Jul 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/lamport-bakery-cpp.html</guid></item><item><title>Karpathy: AI is changing Software Engineering</title><link>https://bytepawn.com/ai-is-changing-software-engineering.html</link><description>&lt;p&gt;Andrej Karpathy says we’re marching from hand-written “Software 1.0” to NN-driven “2.0” and, finally, LLM-powered “3.0.” In my view, neural nets haven’t eaten deterministic code—kernels, browsers, games, and most business logic still run old-school. What has changed is the supply chain: LLM co-pilots (“Software 1.5”) already touch nearly every commit, nudging us towards language and architectures that LLMs are good at. The future belongs to teams that harness those co-pilots while keeping humans firmly on the trigger.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Kiugras Beugras" src="/images/karpathy.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 03 Jul 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/ai-is-changing-software-engineering.html</guid></item><item><title>clamp / median / range</title><link>https://dotat.at/@/2025-07-02-cmp.html</link><description>&lt;p&gt;Here are a few tangentially-related ideas vaguely near the theme of
comparison operators.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#comparison-style"&gt;comparison style&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#clamp-style"&gt;clamp style&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#clamp-is-median"&gt;clamp is median&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#clamp-in-range"&gt;clamp in range&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#range-style"&gt;range style&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#style-clash-"&gt;style clash?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#comparison-style" name="comparison-style"&gt;comparison style&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some languages such as BCPL, Icon, Python have &lt;a href="https://docs.python.org/3/reference/expressions.html#comparisons"&gt;chained comparison
operators&lt;/a&gt;, like&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-py"&gt;    &lt;span class="hilite keyword"&gt;if&lt;/span&gt; &lt;span class="hilite variable"&gt;min&lt;/span&gt; &lt;span class="hilite operator"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="hilite variable"&gt;x&lt;/span&gt; &lt;span class="hilite operator"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="hilite variable"&gt;max&lt;/span&gt;:
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In languages without chained comparison, I like to write comparisons
as if they were chained, like,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;    &lt;span class="hilite keyword"&gt;if&lt;/span&gt; min &amp;lt;= x &amp;amp;&amp;amp; x &amp;lt;= max &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt;
        &lt;span class="hilite comment"&gt;// ...&lt;/span&gt;
    &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A rule of thumb is to prefer less than (or equal) operators and avoid
greater than. In a sequence of comparisons, order values from
(expected) least to greatest.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#clamp-style" name="clamp-style"&gt;clamp style&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;clamp()&lt;/code&gt; function ensures a value is between some min and max,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-py"&gt;    &lt;span class="hilite keyword"&gt;def&lt;/span&gt; &lt;span class="hilite function"&gt;clamp&lt;/span&gt;(&lt;span class="hilite variable"&gt;min&lt;/span&gt;, &lt;span class="hilite variable"&gt;x&lt;/span&gt;, &lt;span class="hilite variable"&gt;max&lt;/span&gt;):
        &lt;span class="hilite keyword"&gt;if&lt;/span&gt; &lt;span class="hilite variable"&gt;x&lt;/span&gt; &lt;span class="hilite operator"&gt;&amp;lt;&lt;/span&gt; &lt;span class="hilite variable"&gt;min&lt;/span&gt;: &lt;span class="hilite keyword"&gt;return&lt;/span&gt; &lt;span class="hilite variable"&gt;min&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;if&lt;/span&gt; &lt;span class="hilite variable"&gt;max&lt;/span&gt; &lt;span class="hilite operator"&gt;&amp;lt;&lt;/span&gt; &lt;span class="hilite variable"&gt;x&lt;/span&gt;: &lt;span class="hilite keyword"&gt;return&lt;/span&gt; &lt;span class="hilite variable"&gt;max&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;return&lt;/span&gt; &lt;span class="hilite variable"&gt;x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I like to order its arguments matching the expected order of the
values, following my rule of thumb for comparisons – and the
description of what &lt;code&gt;clamp()&lt;/code&gt; does. (I used this flavour of &lt;code&gt;clamp()&lt;/code&gt;
in &lt;a href="https://dotat.at/@/2024-08-30-gcra.html"&gt;my article about GCRA&lt;/a&gt;.) But I seem to be unusual in this
preference, based on a few &lt;a href="https://docs.rs/num/latest/num/fn.clamp.html"&gt;example&lt;/a&gt;s I have seen recently.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#clamp-is-median" name="clamp-is-median"&gt;clamp is median&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Last month, &lt;a href="https://mastodon.gamedev.place/@rygorous/114610017178199852"&gt;Fabian Giesen pointed out a way to resolve this
difference of opinion&lt;/a&gt;: A function that returns the median
of three values is equivalent to a &lt;code&gt;clamp()&lt;/code&gt; function that doesn’t
care about the order of its arguments.&lt;/p&gt;
&lt;p&gt;This version is written so that it returns NaN if any of its arguments
is NaN. (When an argument is NaN, both of its comparisons will be false.)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;    &lt;span class="hilite keyword"&gt;fn&lt;/span&gt; &lt;span class="hilite function"&gt;med3&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite variable parameter"&gt;a&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite type builtin"&gt;f64&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable parameter"&gt;b&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite type builtin"&gt;f64&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable parameter"&gt;c&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite type builtin"&gt;f64&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; -&amp;gt; &lt;span class="hilite type builtin"&gt;f64&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;match&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;a &amp;lt;= b&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; b &amp;lt;= c&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; c &amp;lt;= a&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; =&amp;gt; f64&lt;span class="hilite punctuation delimiter"&gt;::&lt;/span&gt;&lt;span class="hilite constructor"&gt;NAN&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;  =&amp;gt; b&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite comment"&gt;//  a &amp;gt; b &amp;gt; c&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; =&amp;gt; a&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite comment"&gt;//  c &amp;gt; a &amp;gt; b&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;  =&amp;gt; c&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite comment"&gt;// b &amp;lt;= c &amp;lt;= a&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; =&amp;gt; c&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite comment"&gt;//  b &amp;gt; c &amp;gt; a&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;  =&amp;gt; a&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite comment"&gt;// c &amp;lt;= a &amp;lt;= b&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;false&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; =&amp;gt; b&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite comment"&gt;// a &amp;lt;= b &amp;lt;= c&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;  &lt;span class="hilite constant builtin"&gt;true&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;  =&amp;gt; b&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite comment"&gt;// a == b == c&lt;/span&gt;
        &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
    &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When two of its arguments are constant, &lt;code&gt;med3()&lt;/code&gt; should compile to the
same code as a simple &lt;code&gt;clamp()&lt;/code&gt;; but &lt;code&gt;med3()&lt;/code&gt;’s misuse-resistance
comes at a small cost when the arguments are not known at compile time.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#clamp-in-range" name="clamp-in-range"&gt;clamp in range&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If your language has proper range types, there is a nicer way to make
&lt;code&gt;clamp()&lt;/code&gt; resistant to misuse:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;    &lt;span class="hilite keyword"&gt;fn&lt;/span&gt; &lt;span class="hilite function"&gt;clamp&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite variable parameter"&gt;x&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite type builtin"&gt;f64&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable parameter"&gt;r&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite type"&gt;RangeInclusive&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;&amp;lt;&lt;/span&gt;&lt;span class="hilite type builtin"&gt;f64&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;&amp;gt;&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; -&amp;gt; &lt;span class="hilite type builtin"&gt;f64&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;let&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite operator"&gt;&amp;amp;&lt;/span&gt;min&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;&lt;span class="hilite operator"&gt;&amp;amp;&lt;/span&gt;max&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; = &lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;r&lt;span class="hilite punctuation delimiter"&gt;.&lt;/span&gt;&lt;span class="hilite function method"&gt;start&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; r&lt;span class="hilite punctuation delimiter"&gt;.&lt;/span&gt;&lt;span class="hilite function method"&gt;end&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;;&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;if&lt;/span&gt; x &amp;lt; min &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt; &lt;span class="hilite keyword"&gt;return&lt;/span&gt; min &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;if&lt;/span&gt; max &amp;lt; x &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt; &lt;span class="hilite keyword"&gt;return&lt;/span&gt; max &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;return&lt;/span&gt; x&lt;span class="hilite punctuation delimiter"&gt;;&lt;/span&gt;
    &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;

    &lt;span class="hilite keyword"&gt;let&lt;/span&gt; x = &lt;span class="hilite function"&gt;clamp&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;x&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite constructor"&gt;MIN&lt;/span&gt;..=&lt;span class="hilite constructor"&gt;MAX&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#range-style" name="range-style"&gt;range style&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://dotat.at/@/2007-06-23-spelling-reform-is-pointless.html"&gt;For a long time&lt;/a&gt; I have been fond of the idea of a simple
counting &lt;code&gt;for&lt;/code&gt; loop that matches the syntax of chained comparisons, like&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-py"&gt;    &lt;span class="hilite keyword"&gt;for&lt;/span&gt; &lt;span class="hilite variable"&gt;min&lt;/span&gt; &lt;span class="hilite operator"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="hilite variable"&gt;x&lt;/span&gt; &lt;span class="hilite operator"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="hilite variable"&gt;max&lt;/span&gt;:
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By itself this is silly: too cute and too ad-hoc.&lt;/p&gt;
&lt;p&gt;I’m also dissatisfied with the range or slice syntax in basically
every programming language I’ve seen. I thought it might be nice if
the cute comparison and iteration syntaxes were aspects of a more
generally useful range syntax, but I couldn’t make it work.&lt;/p&gt;
&lt;p&gt;Until recently when I realised I could make use of prefix or mixfix
syntax, instead of confining myself to infix.&lt;/p&gt;
&lt;p&gt;So now my fantasy pet range syntax looks like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;gt;= min &amp;lt; max    // half-open
    &amp;gt;= min &amp;lt;= max   // inclusive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you might use it in a &lt;a href="https://dotat.at/@/2025-05-13-if-is.html"&gt;pattern match&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if x is &amp;gt;= min &amp;lt; max {
        // ...
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or as an iterator&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    for x in &amp;gt;= min &amp;lt; max {
        // ...
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or to take a slice&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    xs[&amp;gt;= min &amp;lt; max]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-07-02-cmp.html#style-clash-" name="style-clash-"&gt;style clash?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It’s kind of ironic that these range examples don’t follow the
left-to-right, lesser-to-greater rule of thumb that this post started
off with. (&lt;code&gt;x&lt;/code&gt; is not lexically between &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;!)&lt;/p&gt;
&lt;p&gt;But that rule of thumb is really intended for languages such as C that
don’t have ranges. Careful stylistic conventions can help to avoid
mistakes in nontrivial conditional expressions.&lt;/p&gt;
&lt;p&gt;It’s much better if language and library features reduce the need for
nontrivial conditions and catch mistakes automatically.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Wed, 02 Jul 2025 04:33:08 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-07-02-cmp.html</guid></item><item><title>This feed is no longer updated.  Please subscribe to https://susam.net/feed.xml for the new feed.</title><link>https://susam.net/feed.xml</link><description>&lt;p&gt;
This feed is no longer maintained.  Please subscribe to the new feed
at &lt;a href="https://susam.net/feed.xml"&gt;https://susam.net/feed.xml&lt;/a&gt;
to receive updates about new blog posts.
&lt;/p&gt;</description><author>Susam Pal - Discontinued Feed</author><pubDate>Wed, 02 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://susam.net/feed.xml</guid></item><item><title>2025 Mid-Year Review</title><link>https://benovermyer.com/blog/2025/07/2025-mid-year-review/</link><description>&lt;p&gt;Well, 2025 has certainly gone differently than I originally expected on January 1&lt;sup&gt;st&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Much and more has been written about the state of the world in general and the United States of America in particular. I won't go into those topics here.&lt;/p&gt;
&lt;p&gt;In March of this year, I was accepted into the Master of Applied Science in Software Engineering program at Memorial University in St. John's, Newfoundland, Canada. I confirmed the acceptance and began the process of applying for a study permit and related logistics. Today I will be giving notice at my current job at Apiture. By the end of this month, I will be temporarily retiring from industry to become a full-time student again. That will feel strange. Good, I imagine, but strange.&lt;/p&gt;
&lt;p&gt;I really fell off the fitness wagon for several months. Most of the year, really. I've only just gotten back on in the last few days. Now, though, I'm finally going for a daily morning jog again. It feels good, even if I am clearly in much worse shape than I was last year. Well, have to start somewhere. I still have a gym membership until the end of the month and I intend to use it.&lt;/p&gt;
&lt;p&gt;Recently, I've felt that my alcohol consumption has been increasingly deleterious to my health, so I'm easing way off. I did not make quarters 1 and 3 alcohol-free as planned, but I'll make up for it in the latter half of the year.&lt;/p&gt;
&lt;p&gt;In terms of my creative hobby plans, I did continue guitar until the end of May. My guitar will go into storage until I finish my studies, but I do plan to pick it up again. Painting was starting to become a more regular thing for me up until we started needing to pack up. I will definitely be picking that back up too, and maybe sooner than guitar. We'll see.&lt;/p&gt;
&lt;p&gt;My finances have been doing really well. Paying cash for a master's program will put a significant dent in my savings, though.&lt;/p&gt;
&lt;p&gt;Finally... I discovered that I'm allergic to dogs, so I will not be getting a dog. We tried, though. For about a week we fostered a feisty little rescue named Clover. She was some kind of pit mix, and only five months old. Besides being allergic, I couldn't really handle her energy at the time. That made me very sad.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Tue, 01 Jul 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/07/2025-mid-year-review/</guid></item><item><title>Want to meet people, try charging them for it?</title><link>http://notes.eatonphil.com/2025-06-28-want-to-meet-people-charge-them.html</link><description>&lt;p&gt;I have been blogging consistently since 2017. And one of my goals in
speaking publicly was always to connect with like-minded people. I
always left my email and hoped people would get in touch. Even while
my blog and twitter became popular, passing 1M views and 20k
followers, I basically never had people get in touch to chat or meet up.&lt;/p&gt;
&lt;p&gt;So it felt kind of ridiculous when last November I &lt;a href="https://eatonphil.com/chat.html"&gt;started charging
people $100 to chat&lt;/a&gt;. I mean, who am
I? But people started showing up fairly immediately. Now granted the
money did not go to me. It went to an education non-profit and I
merely received the receipt.&lt;/p&gt;
&lt;p&gt;And at this point I've met a number of interesting people, from VCs to
business professors to undergraduate students to founders and everyone
in between. People wanting to talk about trends in databases, about
how to succeed as a programmer, about marketing for developers, and so
on. Women and men thoughout North America, Europe, Africa, New
Zealand, India, Nepal, and so on. And I've raised nearly $6000 for
educational non-profits.&lt;/p&gt;
&lt;p&gt;How is it that you go from giving away your time for free and getting
no hits to charging and almost immediately getting results? For one,
every person responded very positively to it being a fundraiser. It
also helps me be entirely shameless about sharing on social media
every single time someone donates; because it's such a positive thing.&lt;/p&gt;
&lt;p&gt;But also I think that in "charging" for my time it helps people feel
more comfortable about actually taking my time, especially when we
have never met. It gives you a reasonable excuse to take time from
an internet rando.&lt;/p&gt;
&lt;p&gt;On the other hand, a lot of people come for advice and I think giving
advice is pretty dangerous, especially since my background is not
super conventional. I try to always frame things as just sharing my
opinion and my perspective and that they should talk with many others
and not take my suggestions without consideration.&lt;/p&gt;
&lt;p&gt;And there's also the problem that by charging everyone for my time
now, I'm no longer available to people who could maybe use it the
most. I do mention on my page that I will still take calls from people
who don't donate, as my schedule allows. But to be honest I feel less
incentivized to spend time when people do not donate. So I guess this
is an issue with the program.&lt;/p&gt;
&lt;p&gt;But I mitigated even this slightly, and significantly jump-started the
program, during &lt;a href="https://eatonphil.com/30.html"&gt;my 30th birthday&lt;/a&gt; when
I took calls with any person who donated at least $30.&lt;/p&gt;
&lt;p&gt;Anyway, I picked this path because I have wanted to get involved with
helping students figure out their lives and careers. But without a
degree I am literally unqualified for many volunteering programs. And
I always found the time commitments for non-profits painful.&lt;/p&gt;
&lt;p&gt;So until starting this I figured it wouldn't be until I retire that I
find some way to make a difference. But ultimately I kept meeting
people who were starting their own non-profits now or donated
significantly to help students. Peer pressure. I wanted to do my part
now. And 30 minutes of my time in return for a donation receipt has
been an easy trade.&lt;/p&gt;
&lt;p&gt;While only raising a humble $6,000 to date, the &lt;a href="https://eatonphil.com/chat.html"&gt;Chat for
Education&lt;/a&gt; program has been more
successful than I imagined. I've met many amazing people through
it. And it's something that should be easy to keep up indefinitely.&lt;/p&gt;
&lt;p&gt;I hope to meet you through it too!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote about trying to meet like-minded people and fundraising for educational non-profits. &lt;a href="https://t.co/UJ9U6DIHGU"&gt;pic.twitter.com/UJ9U6DIHGU&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1939036004721234248?ref_src=twsrc%5Etfw"&gt;June 28, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 28 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-06-28-want-to-meet-people-charge-them.html</guid></item><item><title>A remote adventure in the Southern Highlands of Iceland</title><link>https://theroadchoseme.com/a-remote-adventure-in-the-southern-highlands-of-iceland</link><description>The &amp;#8220;F&amp;#8221; roads in Iceland lead to incredibly stunning places and traverse some pretty remote and rugged terrain. After meeting a few locals and getting the inside word, I learn the really, really good&amp;#46;&amp;#46;&amp;#46;</description><author>The Road Chose Me</author><pubDate>Fri, 27 Jun 2025 20:45:20 GMT</pubDate><guid isPermaLink="true">https://theroadchoseme.com/a-remote-adventure-in-the-southern-highlands-of-iceland</guid></item><item><title>Golang and Let's Encrypt: a free software story</title><link>https://dotat.at/@/2025-06-28-boulder.html</link><description>&lt;p&gt;Here’s a story from nearly 10 years ago.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-28-boulder.html#the-bug" name="the-bug"&gt;the bug&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I think it was my friend &lt;a href="https://www.greenend.org.uk/rjk/"&gt;Richard Kettlewell&lt;/a&gt;
who told me about a bug he encountered with Let’s Encrypt in its early
days in autumn 2015: &lt;a href="https://github.com/letsencrypt/boulder/issues/1197"&gt;it was failing to validate mail domains
correctly&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-28-boulder.html#the-context" name="the-context"&gt;the context&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At the time I had previously been responsible for Cambridge
University’s email anti-spam system for about 10 years, and in 2014 I
had been given responsibility for Cambridge University’s DNS. So I
knew how Let’s Encrypt should validate mail domains.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Let%27s_Encrypt"&gt;Let’s Encrypt&lt;/a&gt; was about one year old. Unusually, the code
that runs their operations, &lt;a href="https://github.com/letsencrypt/boulder"&gt;Boulder&lt;/a&gt;, is free software and open to
external contributors.&lt;/p&gt;
&lt;p&gt;Boulder is written in Golang, and I had not previously written any
code in Golang. But its reputation is to be easy to get to grips with.&lt;/p&gt;
&lt;p&gt;So, in principle, the bug was straightforward for me to fix. How
difficult would it be as a Golang newbie? And what would Let’s
Encrypt’s contribution process be like?&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-28-boulder.html#the-hack" name="the-hack"&gt;the hack&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I cloned the Boulder repository and had a look around the code.&lt;/p&gt;
&lt;p&gt;As is pretty typical, there are a couple of stages to fixing a bug in
an unfamiliar codebase:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;work out where the problem is&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;try to understand if the obvious fix could be better&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, I remember discovering a relatively substantial TODO
item that intersected with the bug. I can’t remember the details, but
I think there were wider issues with DNS lookups in Boulder. I decided
it made sense to fix the immediate problem without getting involved in
things that would require discussion with Let’s Encrypt staff.&lt;/p&gt;
&lt;p&gt;I faffed around with the code and pushed something that looked like it
might work.&lt;/p&gt;
&lt;p&gt;A fun thing about this hack is that I never got a working Boulder test
setup on my workstation (or even Golang, I think!) – I just relied on
the Let’s Encrypt cloud test setup. The feedback time was very slow,
but it was tolerable for a simple one-off change.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-28-boulder.html#the-fix" name="the-fix"&gt;the fix&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/letsencrypt/boulder/pull/1230"&gt;My pull request&lt;/a&gt;
was small, &lt;span style="color: green;"&gt;+48&lt;/span&gt;&lt;span style="color: red;"&gt;-14&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;After a couple of rounds of review and within a few days, it was
merged and put into production!&lt;/p&gt;
&lt;p&gt;A pleasing result.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-28-boulder.html#the-upshot" name="the-upshot"&gt;the upshot&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I thought Golang (at least as it was used in the Boulder codebase) was
as easy to get to grips with as promised. I did not touch it again
until several years later, because there was no need to, but it seemed
fine.&lt;/p&gt;
&lt;p&gt;I was very impressed by the Let’s Encrypt continuous integration and
automated testing setup, and by their low-friction workflow for
external contributors. One of my fastest drive-by patches to get into
worldwide production.&lt;/p&gt;
&lt;p&gt;My fix was always going to be temporary, and all trace of it was
overwritten years ago. It’s good when “temporary” turns out to be
true!&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-28-boulder.html#the-point" name="the-point"&gt;the point&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I was reminded of this story in the pub this evening, and I thought it
was worth writing down. It demonstrated to me that Let’s Encrypt
really were doing all the good stuff they said they were doing.&lt;/p&gt;
&lt;p&gt;So thank you to Let’s Encrypt for providing an exemplary service and
for giving me a happy little anecdote.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Thu, 26 Jun 2025 04:50:23 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-06-28-boulder.html</guid></item><item><title>The discreet charm of the infrastructureless</title><link>http://blog.jgc.org/2025/06/the-discreet-charm-of-infrastructureless.html</link><description>&lt;p&gt;Early in the morning in a European city, I awoke and wondered for a moment where I was. The room was totally black apart from a small glow from the face of my Casio Lineage watch lying on the bedside table. The luminous dial had absorbed sunlight during the day and was now giving off gentle light in the night; enough brightness to read the time.&lt;/p&gt;&lt;p&gt;&lt;i&gt;Infrastructureless #1: sunlight + chemistry = glow in the dark.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;The watch had also absorbed sunlight through its face during the day to recharge its battery. In all the time I've owned this watch I've never seen the battery drop below the what Casio calls HI (there are levels MID and LO but I'd only seen MID once when I took the watch from its original packaging)&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtMzGp6AjbrtJnb89q-HqxiiWVeTRnyhOJ7CNlTzdxiBLyOoi419sLDoFyC-BXScCOlfNkaV9HtHJKE_F3-Q8GIGpcTVJ9gEXCHe1KWzY_pC6w1TQICU1eCoHDhevi3rLQez3EQ2RalUHkwrmzkqEHO600zgNwBXuNih-YYBa5WlrGS6CmGUvgOQ/s1882/casio-1.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtMzGp6AjbrtJnb89q-HqxiiWVeTRnyhOJ7CNlTzdxiBLyOoi419sLDoFyC-BXScCOlfNkaV9HtHJKE_F3-Q8GIGpcTVJ9gEXCHe1KWzY_pC6w1TQICU1eCoHDhevi3rLQez3EQ2RalUHkwrmzkqEHO600zgNwBXuNih-YYBa5WlrGS6CmGUvgOQ/w281-h400/casio-1.png" width="281" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;i&gt;Infrastructureless #2: sunlight + semiconductors = electrical power.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;Clicking the top right button to illuminate the face further I could see the small LED display showing the local time in the European city (the main hands show my home time zone). And on the LED display a little black rectangle under RCVD indicated that somewhere in the night (it turned out at 02:04) the watch had picked up the signal from the &lt;a href="https://en.wikipedia.org/wiki/DCF77"&gt;DCF77&lt;/a&gt; transmitter in Germany and set itself.&lt;/p&gt;&lt;p&gt;&lt;i&gt;Infrastructureless #3: radio = correct time.&lt;/i&gt;&lt;/p&gt;&lt;div&gt;OK, DCF77 isn't totally without infrastructure, but one of the reasons I became a radio amateur is lack of infrastructure. Sure, you're able to read this because of the Internet but it's an incredibly complex system: layers and layers of technology and cooperating entities just so you can read this.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On the other hand, radio amateurs just send out electromagnetic waves and talk to each other, no intervening infrastructure (well, ok, maybe you can count the ionosphere as infrastructure allowing long distance reception). And that's what DCF77, and the other five transmitters the Casio can receive in the US, UK, China and Japan, are doing: sending waves into the night to be picked up and decoded by whoever is listening.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj05muy8FG1W4z3XdzOnVMkNPX-0kxnLY-ASyyH6vu06vlSOmPIxoYQbPWiuFUsNRLjfjGP_I-eCT00OxcNFNaq34MoJJ3WOU4JdSvGFWavsmC9HE-01lTsUhdgQWC3MZa26A4iIT0Sqtw0ba_JPoxH5vVTgkmSenioooM6iUhTCoTonRE_3GihfQ/s1158/casio-3.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj05muy8FG1W4z3XdzOnVMkNPX-0kxnLY-ASyyH6vu06vlSOmPIxoYQbPWiuFUsNRLjfjGP_I-eCT00OxcNFNaq34MoJJ3WOU4JdSvGFWavsmC9HE-01lTsUhdgQWC3MZa26A4iIT0Sqtw0ba_JPoxH5vVTgkmSenioooM6iUhTCoTonRE_3GihfQ/w640-h318/casio-3.png" width="640" /&gt;&lt;/a&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj05muy8FG1W4z3XdzOnVMkNPX-0kxnLY-ASyyH6vu06vlSOmPIxoYQbPWiuFUsNRLjfjGP_I-eCT00OxcNFNaq34MoJJ3WOU4JdSvGFWavsmC9HE-01lTsUhdgQWC3MZa26A4iIT0Sqtw0ba_JPoxH5vVTgkmSenioooM6iUhTCoTonRE_3GihfQ/s1158/casio-3.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNnQPIfLy4xjVok5m6dyLO2-ZVuHfUo0SROii0ekUpdhvVFFGsQEXR8AGggGIeUWtPTFKtNIxa-D9hDYoE-190vG9kyteWrVAH488uAmxx4-pGFCcKY4PSZJrFCZbyq1WwdoYfbRh8jLAExwB8AlAmVs7_bDQMRoiE4OtGhtfUK5hRM8qt9ubWiQ/s1112/casio-2.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="352" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNnQPIfLy4xjVok5m6dyLO2-ZVuHfUo0SROii0ekUpdhvVFFGsQEXR8AGggGIeUWtPTFKtNIxa-D9hDYoE-190vG9kyteWrVAH488uAmxx4-pGFCcKY4PSZJrFCZbyq1WwdoYfbRh8jLAExwB8AlAmVs7_bDQMRoiE4OtGhtfUK5hRM8qt9ubWiQ/w640-h352/casio-2.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;span style="color: #0000ee;"&gt;&lt;span&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Also, I'm just fascinated by radio propagation: my ancient Sharper Image clock sometimes &lt;a href="https://blog.jgc.org/2021/07/receiving-wwvb-time-signal-in-portugal.html"&gt;picks up the US WWVB in Portugal&lt;/a&gt;.&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Wed, 25 Jun 2025 11:30:21 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/06/the-discreet-charm-of-infrastructureless.html</guid></item><item><title>What is Simplicity?</title><link>https://3059274a.danpalmer-me.pages.dev/2025-06-25-what-is-simplicity/</link><description>&lt;p&gt;It&amp;rsquo;s uncontroversial to say that the code we write should be simple. In a discipline that is all about grappling with complexity, keeping things simple is critical to software development efforts, team productivity, scalability, and maintainability. Despite well known satirical counter-examples such as &lt;a href="https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition"&gt;Enterprise FizzBuzz&lt;/a&gt;, simplicity is a goal for most software engineers.&lt;/p&gt;
&lt;p&gt;But what is simplicity? Despite this near universal agreement that it&amp;rsquo;s important, throughout my career I&amp;rsquo;ve seen numerous instances of friction in design and code review where both parties are convinced that their solution is the simpler option. These disagreements are notoriously hard to resolve because &lt;strong&gt;we lack the language to talk about our preferences when it comes to simplicity&lt;/strong&gt;, and simplicity is not a single concept or direction, but a set of competing priorities and trade-offs.&lt;/p&gt;</description><author>Dan Palmer</author><pubDate>Wed, 25 Jun 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://3059274a.danpalmer-me.pages.dev/2025-06-25-what-is-simplicity/</guid></item><item><title>Guide to Working Weekends</title><link>https://jodavaho.io/posts/guide-to-working-weekends.html</link><description>&lt;p&gt;Coffee on a monday after a productive weekend, here&amp;rsquo;s what went well this time. Much better than usual!&lt;/p&gt;
&lt;h1 id="dont-work-weekends"&gt;Don&amp;rsquo;t work weekends&lt;/h1&gt;
&lt;p&gt;If at all possible, keep time to yourself on the weekend. Use it to read,
relax, catch up on housework, do hobbies, spend time with wife or kids (or date
otherwise). Etc. You &lt;em&gt;need&lt;/em&gt; breaks, mentally, to remain sharp, and physically,
to remain energetic.&lt;/p&gt;
&lt;p&gt;So, it should be the default mode to &lt;em&gt;not&lt;/em&gt; work on the weekends. But, sometimes
that thing is broken, the deadline is looming, the competition is knocking,
etc&amp;hellip;&lt;/p&gt;
&lt;p&gt;So how do you recharge (or at least not ruin your next week), while still being
productive? Here&amp;rsquo;s what just worked for me.&lt;/p&gt;
&lt;h1 id="have-a-supportive-partner"&gt;Have a supportive partner&lt;/h1&gt;
&lt;p&gt;The best weekend warrior advice I can give is to have a supportive partner,
which is also the best life I can give. Partner up with someone who knows and
appreciates what you&amp;rsquo;re going though, preferably having been there before. My
powerhouse wife has a 10x work ethic over me, and also (somewhat telling) has
10x the work boundaries that I do.&lt;/p&gt;
&lt;h1 id="tell-people-you-need-the-time"&gt;Tell people you need the time&lt;/h1&gt;
&lt;p&gt;Give time family, friends, or anyone else who is counting on you to adjust
their plans. I had been telling my wife for two weeks I was going to be in a
crunch and needed the time. We worked together to make that time.&lt;/p&gt;
&lt;h1 id="work-on-highest-net-happiness-tasks"&gt;Work on highest net happiness tasks&lt;/h1&gt;
&lt;p&gt;Maybe there&amp;rsquo;s a bit of work you wanted to do, but it wasn&amp;rsquo;t the highest
priority for everyone. Do that now! It&amp;rsquo;s your time, and assuming you&amp;rsquo;re a
decent employee, you gave it everything you had during the week. If you must
give more, at least prioritize the tasks that will provide the most relief or
enjoyment once they are done.&lt;/p&gt;
&lt;h1 id="take-breaks"&gt;Take breaks&lt;/h1&gt;
&lt;p&gt;Code has to compile, emails might need a reply, Claude is busy having an
existential crisis. Either way, go outside, to the gym, read a little, watch
part of a movie, vacuum the floor, play with legos, listen to an audiobook.
Hell, go have a margarita on the back porch. This time is yours, spend it how
you want.&lt;/p&gt;
&lt;p&gt;Unless your livelihood is threatened with imminent demise (and it almost never
is, even if your emotions might say it is), you should &lt;em&gt;not&lt;/em&gt; be over-exerting
yourself during personal time.&lt;/p&gt;
&lt;h1 id="be-alone"&gt;Be alone&lt;/h1&gt;
&lt;p&gt;There&amp;rsquo;s something really awful about explaining to your kids that you cannot go
play bubbles outside because you have to work. For me, at least, even seeing my
wife go about her day pulls me away from work.&lt;/p&gt;
&lt;p&gt;Worse, if you&amp;rsquo;re following this advice, and everyone is around to see you take
a margarita break during a Claude task, you might be in for some difficult
misunderstandings. &amp;ldquo;I thought you had to work!&amp;rdquo; is the seed for guilt (for you)
and feelings of being taken advantage of (for them).&lt;/p&gt;
&lt;p&gt;Send the kids, partner, etc to a spa day or trip to grandparents, maybe, but
don&amp;rsquo;t add to the guilt by having them mope around the house while you burn
yourself out.&lt;/p&gt;
&lt;h1 id="make-it-up-to-them"&gt;Make it up to them&lt;/h1&gt;
&lt;p&gt;No doubt your spouse is pulling extra weight while you&amp;rsquo;re working. Or your
friends are noticing you missed the event. I can&amp;rsquo;t emphasize enough:&lt;/p&gt;
&lt;p&gt;Life is short, and consistently distancing yourself from those you love &lt;em&gt;will&lt;/em&gt;
have long term consequences for your happiness.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll want to make it up to them. Do extra work around the house, cover your
partner when it is their turn. Buy dinner for friends next time.&lt;/p&gt;
&lt;h1 id="have-something-to-show"&gt;Have something to show&lt;/h1&gt;
&lt;p&gt;For employer, family, and friends, you want to have something good to show for
the extra time. Tell your spouse you&amp;rsquo;re in a better place now, give your boss
some extra results, etc. Check in Monday morning right away with something to
show.&lt;/p&gt;
&lt;p&gt;Now, there&amp;rsquo;s a risk of coming across as a performative worker. Those are &lt;em&gt;the
worst&lt;/em&gt;. Depending on your team culture, it might be best to keep the results
just between you and the boss, or to somewhat downplay them. Weigh the
tradeoffs, but if someone had to bend their schedule to allow you to work when
you normally don&amp;rsquo;t, you better have something to show for it.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 23 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/guide-to-working-weekends.html</guid></item><item><title>Debugging memory leaks in Postgres, jemalloc edition</title><link>http://notes.eatonphil.com/2025-06-21-debugging-memory-leaks-postgres-jemalloc-edition.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/debugging-memory-leaks-postgres-jemalloc-edition"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 21 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-06-21-debugging-memory-leaks-postgres-jemalloc-edition.html</guid></item><item><title>Writing a simple async message queue server in C++, Python and JavaScript — Summary</title><link>https://bytepawn.com/writing-a-simple-async-message-queue-cpp-python-javascript-summary.html</link><description>&lt;p&gt;I summarize thoughts and lessons learned of implementing the same async message queue server in C++, Python and JavaScript over the past 2 years.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/cpp-py-js.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 21 Jun 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-async-message-queue-cpp-python-javascript-summary.html</guid></item><item><title>Writing a simple C++20 async message queue server - Part III</title><link>https://bytepawn.com/writing-a-simple-cpp-async-message-queue-server-part-iii.html</link><description>&lt;p&gt;I write the final version of the async message queue server in modern C++20, wire compatible with the Python and Javascript versions.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/cpp_coroutines.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 20 Jun 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-cpp-async-message-queue-server-part-iii.html</guid></item><item><title>100 blog posts, 6 years, 5 million views</title><link>https://austinhenley.com/blog/100.html</link><description>&lt;a href="https://austinhenley.com/blog/100.html"&gt;https://austinhenley.com/blog/100.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Thu, 19 Jun 2025 22:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/100.html</guid></item><item><title>Agentic Coding Workflow &amp;amp; Cline Demo</title><link>https://smcleod.net/2025/06/agentic-coding-workflow-cline-demo/</link><description>Recording of a demo and Q&amp;amp;A session on my Agentic Coding workflow and Cline use.</description><author>smcleod.net</author><pubDate>Thu, 19 Jun 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/06/agentic-coding-workflow-cline-demo/</guid></item><item><title>How SaaS Startups Can Scale Accounting Without Slowing Development</title><link>https://caseysoftware.com/blog/how-saas-startups-can-scale-accounting-without-slowing-development?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=how-saas-startups-can-scale-accounting-without-slowing-development</link><description>&lt;p&gt;SaaS startups live and die by their ability to iterate. Every feature launch, bug fix, or product tweak hinges on&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/how-saas-startups-can-scale-accounting-without-slowing-development"&gt;How SaaS Startups Can Scale Accounting Without Slowing Development&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Wed, 18 Jun 2025 15:53:00 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/how-saas-startups-can-scale-accounting-without-slowing-development?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=how-saas-startups-can-scale-accounting-without-slowing-development</guid></item><item><title>Zero Time Dilemma review</title><link>https://burakku.com/blog/zero-time-dilemma-review/</link><description>&lt;p&gt;&lt;img alt="Zero Time Dilemma" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The final stretch.&lt;/p&gt;
&lt;p&gt;Everyone knows that Uchikoshi loves nothing more than non-linear stories, and Zero Time Dilemma might be him taking it to its ultimate conclusion. It feels like Uchikoshi wrote the final part of the Zero Escape series, fed most of it through a woodchipper and then made you scavenge the plot in a random order from the remains. The game lasts for about 20 hours and you’ll spend at least the first ten hours absolutely clueless as to what is happening – and when.&lt;/p&gt;
&lt;p&gt;Even though the previous Zero Escape games are definitely non-linear stories too, at least you had a sense of belonging in the story. Most of the game feels like such a mess that I was worried if they could even to sort everything out in a manner that makes sense. Thankfully the story starts to mesh together in the end, at least to a certain point. But before that meshing starts to happen, you really need to cling onto your love for Zero Escape to get through the drudgery and confusion. I was sure that I’d recommend against this game during the first half.&lt;/p&gt;
&lt;p&gt;The drudgery is not helped at all by the graphics. Technically speaking, the level of the 3D graphics has taken a significant leap since Virtue’s Last Reward. And yet, despite the objective advancements, the end result is arguably worse. Animation quality is laughably poor, especially the lip syncing, which amounts to random lip movements completely divorced from the spoken lines. It’s even worse than the sockpuppet-like mouth flapping of Virtue’s Last Reward, since at least there was some connection between the audio and the animation. The low-poly VLR models doing a Sesame Street impression didn’t look nearly as awkward as the more realistic models of Zero Time Dilemma. I think there was also an instance where a character was supposed to look shocked or mortified during a life-and-death scenario, and it ended up looking like he was visibly bored.&lt;/p&gt;
&lt;p&gt;And even though the 3D quality is higher than Virtue’s Last Reward, it’s still not that stellar. However janky The Somnium Files games might look, they’re still doing circles around Zero Time Dilemma. I would’ve liked to see a lot more work towards the models and the textures. Presumably the developers either had limited skillsets for 3D work, or they simply didn’t have the budget to spend the time. But what didn’t need as much work as it did was the character designs: returning characters don’t look at all like their previous selves despite there only being some months since we’ve seen them in 999 or VLR. Akane and Junpei can really only be identified by their names and voices, as their looks are completely different, making it much harder to treat them as the old characters. Sigma and Phi aren’t quite as bad but the differences still catch the eye.&lt;/p&gt;
&lt;p&gt;The redesign of the old characters isn’t helped at all that quite a few of the returning characters don’t really even act like themselves in the story. This is even noted in the game, especially with Junpei, but we never really get great explanations for it. Sigma also acts out in a pretty weird way considering the life he’s lived thus far as detailed in Virtue’s Last Reward. I was sorta expecting there to be a plot twist about one of the returning characters being an impostor, but no, these are the real deals.&lt;/p&gt;
&lt;p&gt;And the graphics aren’t even the only problem! The resolution selection in the launcher didn’t seem to work so I just set the resolution by rescaling the window and ended with the uncommon resolution of 1729 × 979. Probably should’ve just edited the configuration file directly. They’ve also landed on a worse control scheme than Virtue’s Last Reward, so I ended up using the mouse for way more things than I wanted. In VLR, I used the keyboard a lot more for camera control and for inventory usage. And considering the sheer amount of text inputs in the game, was it that much hard to implement keyboard typing? You really can tell that this is a console port.&lt;/p&gt;
&lt;p&gt;On the core gameplay front, gone is the visual novel style storytelling. The story is instead told as (poorly-animated) cutscenes from a third-person perspective. While I understand the reasoning for it, since it ties into the storytelling with the revolving character focus, I do still much prefer the first-person perspectives of the prior instalments. It made you feel like a participant in the games and not a mere observer, robbed of all agency.&lt;/p&gt;
&lt;p&gt;The escape room segments aren’t too dissimilar from the previous entries. I’d say that the difficulty level is somewhere between the straight-and-direct 999 escape segments and the more obtuse VLR escapes. You probably still want to have a notebook at hand like with Virtue’s Last Reward, as there’s quite a few things to remember. What was surprising was that there’s actually not that many escape segments. I got the achievement for solving everything with like a quarter of the game left.&lt;/p&gt;
&lt;p&gt;Once you get to a point where the story starts coming together, it does start to feel like proper good old Zero Escape. To a point where I reconsidered my earlier opinion and felt like it was a game worth playing, even if was clearly a much weaker presentation than the earlier titles. And as per usual in the franchise, you also get some plot twists to open up the mystery. And when I got to the big plot twist, my faith started faltering again. It just felt like it was reaching a bit too much, a bit of an asspull. At this point I was once again starting to reconsider whether or not I actually liked this game.&lt;/p&gt;
&lt;p&gt;Quite honestly, I’m still not sure. While the big twist wasn’t completely without any setup, the foreshadowing was extremely subtle. In the previous Zero Escape titles, I felt like Uchikoshi had thoroughly bamboozled me when I got an unexpected twist, and here my first reaction was that he’d cheated me. Even after scouring the Internet for all of the foreshadowing in the game, it still feels more unfair than the foreshadowing in Virtue’s Last Reward. I guess it is at least thematically fitting, as the game is about the inherent unfairness of life. Maybe this game is just as unfair.&lt;/p&gt;
&lt;p&gt;There’s also things about the story that feel like jumping the shark. The Zero Escape world has always been pretty out there, with elements of sci-fi, pseudoscience and the supernatural all thrown in there. Still, it feels like Zero Time Dilemma takes it maybe a bit too far, with new and even wilder things coming out on a regular basis. I can’t be too mad about it, but I also can’t overlook it. They just seem like extremely crazy concepts that are mostly there as convenient plot devices.&lt;/p&gt;
&lt;p&gt;Zero Time Dilemma – the first half of the story is a mess, the visual presentation feels like a bad student project, technical side has significant regressions from the prior games, the storytelling gameplay is less engaging, the big twist is potentially maddening, and some developments take it a bit too far. This doesn’t sound like a winning combination to me. And yet, I feel like I can at least somewhat recommend playing through it, despite its many flaws. This assuming that you are already invested into the Zero Escape world. Anyone else should run away screaming.&lt;/p&gt;
&lt;p&gt;While some of the developments range from confusing to maddening, there’s still an interesting plot once it starts to come together. Uchikoshi at his worst is still Uchikoshi. Zero Time Dilemma also manages to do the thing that I was most expecting from it: shed some light on what questions remained from Virtue’s Last Reward. After I finished playing through the game, I asked myself if I regretted spending 20 hours on it, and the answer was “no”. Thankfully, as the final entry in the Zero Escape trilogy, it also doesn’t leave us players hanging for a continuation. Probably for the best, since who knows how hard Uchikoshi would need to stretch to continue the story and to pull twists on us again.&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 18 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/zero-time-dilemma-review/</guid></item><item><title>Sigh, here we go again.</title><link>https://blog.rongarret.info/2025/06/sigh-here-we-go-again.html</link><description>You would think that after the disasters in Afghanistan and Iraq that Republicans would have learned that starting a war in that part of the world is a Really Bad Idea (tm).&amp;nbsp; But no.&amp;nbsp; After utterly failing to bring about regime change in both its eastern and western neighbors, the Trump administration is winding up to try yet again again in Iran.&amp;nbsp; Maybe the third time will be the</description><author>Rondam Ramblings</author><pubDate>Wed, 18 Jun 2025 01:07:45 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/06/sigh-here-we-go-again.html</guid></item><item><title>No, Science is Not Just Another Religion</title><link>https://blog.rongarret.info/2025/05/no-science-is-not-just-another-religion.html</link><description>I want to debunk once and for all this idea that "science is just another religion".&amp;nbsp; It isn't, for one simple reason: all religions are based on some kind of metaphysical assumptions.&amp;nbsp; Those assumptions are generally something like the authority of some source of revealed knowledge, typically a holy text.&amp;nbsp; But it doesn't have to be that.&amp;nbsp; It can be as simple as assuming that</description><author>Rondam Ramblings</author><pubDate>Tue, 17 Jun 2025 19:35:31 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/05/no-science-is-not-just-another-religion.html</guid></item><item><title>What makes a good side project?</title><link>https://austinhenley.com/blog/goodproject.html</link><description>&lt;a href="https://austinhenley.com/blog/goodproject.html"&gt;https://austinhenley.com/blog/goodproject.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Tue, 17 Jun 2025 03:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/goodproject.html</guid></item><item><title>Finding Dead Websites</title><link>https://www.marginalia.nu/log/a_122_dead_websites/</link><description>&lt;p&gt;As some of the work planned for Marginalia Search this year has been progressing a bit faster than anticipated, there was time to implement an unplanned change.&lt;/p&gt;
&lt;p&gt;This post details the implementation of a system for detecting when servers are online, to avoid serving dead links and improve data quality, and for detecting when websites have significant changes including ownership transfers and parking.&lt;/p&gt;
&lt;h1 id="table-of-contents"&gt;Table Of Contents&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#rationale"&gt;Feature Rationale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#repr"&gt;Data Representation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#livedata"&gt;Live Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#eventdata"&gt;Event Data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#details"&gt;Change Detection Details&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#availability"&gt;Availability Detection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#ownership"&gt;Ownership Changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#dns"&gt;DNS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#hurdles"&gt;Implementation Hurdles&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#scheduling"&gt;Scheduling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#certvalid"&gt;Certificate Validation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marginalia.nu/log/a_122_dead_websites/#conclusion"&gt;Conclusions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="rationale"&gt;&lt;/a&gt;&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 17 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_122_dead_websites/</guid></item><item><title>A new adventure</title><link>https://blog.yossarian.net/2025/06/17/a-new-adventure</link><description>This is a personal announcement post: after 7 years at Trail of Bits, I’m leaving to do something new. Specifically, I’ll be joining Astral to help them build the next generation of Python developer tooling.</description><author>ENOSUCHBLOG</author><pubDate>Tue, 17 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/06/17/a-new-adventure</guid></item><item><title>Running and storing 3+ million LLM AI requests without spending $100,000</title><link>https://boyter.org/posts/three-million-llm-requests/</link><description>&lt;p&gt;With the rise of AI LLM technology, I naturally wanted to learn more about using it. Both from a what can it do but also where can I use it in various tasks. Since I am also investing more time in searchcode.com these days I wanted to find a way I could use these tools to improve it.&lt;/p&gt;
&lt;p&gt;One of the easiest things I could think of was by creating summaries of each file. This could then be used in both the HTML title, description and on the page assisting users with what they are looking at. I find these fairly useful at times, and I quite often ask LLM&amp;rsquo;s what something does when working with unfamiliar languages or codebases.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 16 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/three-million-llm-requests/</guid></item><item><title>If the Ten Commandments Reflected Reality</title><link>https://blog.rongarret.info/2025/06/if-ten-commandments-reflected-reality.html</link><description>And the Lord spoke unto Moses, saying: I am the Lord your God, who brought you out of Egypt, out of the land of slavery.You shall have no other gods before me.&amp;nbsp; Except Donald Trump.&amp;nbsp; If he says something that goes against my word, you shall believe him and not me.You shall not make for yourself any image in the form of anything in heaven above or on the earth beneath or in the waters</description><author>Rondam Ramblings</author><pubDate>Sun, 15 Jun 2025 21:44:42 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/06/if-ten-commandments-reflected-reality.html</guid></item><item><title>The fastest way to detect a vowel in a string</title><link>https://austinhenley.com/blog/vowels.html</link><description>&lt;a href="https://austinhenley.com/blog/vowels.html"&gt;https://austinhenley.com/blog/vowels.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Fri, 13 Jun 2025 22:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/vowels.html</guid></item><item><title>Bypassing GitHub Actions policies in the dumbest way possible</title><link>https://blog.yossarian.net/2025/06/11/github-actions-policies-dumb-bypass</link><description>TL;DR: GitHub Actions provides a policy mechanism for limiting the kinds of actions and reusable workflows that can be used within a repository, organization, or entire enterprise. Unfortunately, this mechanism is trivial to bypass. GitHub has told me that they don’t consider this a security issue (I disagree), so I’m publishing this post as-is.</description><author>ENOSUCHBLOG</author><pubDate>Wed, 11 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yossarian.net/2025/06/11/github-actions-policies-dumb-bypass</guid></item><item><title>Low-background Steel: content without AI contamination</title><link>http://blog.jgc.org/2025/06/low-background-steel-content-without-ai.html</link><description>&lt;p&gt;Somehow I forgot to blog my site: &lt;a href="https://lowbackgroundsteel.ai/"&gt;https://lowbackgroundsteel.ai/&lt;/a&gt;. I created it back in March 2023 as a clearinghouse for online resources that hadn't been contaminated with AI-generated content.&amp;nbsp;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijWm8XoAHKZn2-fYIvZZXJFJLv4ASg4T0nmvExAee9kk32vI4V3gln9PpYKQBfJkMsae-fJvuo1A_cRI-ZX6E5i0f92RCHm5MXtZNHZr3ki3f3sX4n9HtlwwZCQVVGWigHYLhTJbz-pZQvRm0O5N6If_LqG6Cw2A9InEtGuBD4-mPqRtdc7NUBMQ/s2370/lbs.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijWm8XoAHKZn2-fYIvZZXJFJLv4ASg4T0nmvExAee9kk32vI4V3gln9PpYKQBfJkMsae-fJvuo1A_cRI-ZX6E5i0f92RCHm5MXtZNHZr3ki3f3sX4n9HtlwwZCQVVGWigHYLhTJbz-pZQvRm0O5N6If_LqG6Cw2A9InEtGuBD4-mPqRtdc7NUBMQ/w558-h640/lbs.jpeg" width="558" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Low-background_steel"&gt;Low-background Steel &lt;/a&gt;(and lead) is a type of metal uncontaminated by radioactive isotopes from nuclear testing. That steel and lead is usually recovered from ships that sunk before the Trinity Test in 1945. The site is about uncontaminated content that I'm terming "Low-background Steel". The idea is to point to sources of text, images and video that were created prior to the explosion of AI-generated content that occurred in 2022.&lt;/p&gt;&lt;p&gt;It currently contains pointers to a Wikipedia dump from prior to the release of ChatGPT, the Arctic Code Vault, Project Gutenberg, and more.&lt;/p&gt;&lt;p&gt;If you know of other sources of non-contaminated content plus &lt;a href="https://lowbackgroundsteel.ai/submit"&gt;submit them&lt;/a&gt;!&lt;/p&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Tue, 10 Jun 2025 21:00:21 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/06/low-background-steel-content-without-ai.html</guid></item><item><title>performance of random floats</title><link>https://dotat.at/@/2025-06-08-floats.html</link><description>&lt;p&gt;A couple of years ago I wrote about &lt;a href="https://dotat.at/@/2023-06-23-random-double.html"&gt;random floating point
numbers&lt;/a&gt;. In that article I was mainly concerned about how
neat the code is, and I didn’t pay attention to its performance.&lt;/p&gt;
&lt;p&gt;Recently, &lt;a href="https://lobste.rs/s/l8y8gg/where_did_random_go_wrong#c_yrvysr"&gt;a comment from Oliver Hunt&lt;/a&gt; and &lt;a href="https://purplesyringa.moe/blog/fast-limited-range-conversion-between-ints-and-floats/"&gt;a blog post from
Alisa Sireneva&lt;/a&gt; prompted me to wonder if I made an
unwarranted assumption. So I wrote a little benchmark, which you can
find in &lt;a href="https://dotat.at/cgi/git/pcg-dxsm.git/blob/HEAD:/floats.c"&gt;&lt;code&gt;pcg-dxsm.git&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(Note 2025-06-09: I’ve edited this post substantially after
discovering some problems with the results.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#recap"&gt;recap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#code"&gt;code&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#bithack"&gt;bithack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#multiply"&gt;multiply&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#benchmark"&gt;benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#results"&gt;results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#conclusion"&gt;conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#recap" name="recap"&gt;recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Briefly, there are two basic ways to convert a random integer to a
floating point number between 0.0 and 1.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use bit fiddling to construct an integer whose format matches a
float between 1.0 and 2.0; this is the same span as the result but
with a simpler exponent. Bitcast the integer to a float and
subtract 1.0 to get the result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Shift the integer down to the same range as the mantissa, convert
to float, then multiply by a scaling factor that reduces it to the
desired range. This produces one more bit of randomness than the
bithacking conversion.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(There are other &lt;a href="https://dotat.at/@/2023-06-23-random-more.html"&gt;less basic ways&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#code" name="code"&gt;code&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The double precision code for the two kinds of conversion is below.
(Single precision is very similar so I’ll leave it out.)&lt;/p&gt;
&lt;p&gt;It’s mostly as I expect, but there are a couple of ARM instructions
that surprised me.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#bithack" name="bithack"&gt;bithack&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The bithack function looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;    &lt;span class="hilite type"&gt;double&lt;/span&gt; &lt;span class="hilite function"&gt;bithack52&lt;/span&gt;(&lt;span class="hilite type"&gt;uint64_t&lt;/span&gt; &lt;span class="hilite variable"&gt;u&lt;/span&gt;) {
        &lt;span class="hilite variable"&gt;u&lt;/span&gt; &lt;span class="hilite operator"&gt;=&lt;/span&gt; ((&lt;span class="hilite type"&gt;uint64_t&lt;/span&gt;)(&lt;span class="hilite number"&gt;1023&lt;/span&gt;) &amp;lt;&amp;lt; &lt;span class="hilite number"&gt;52&lt;/span&gt;) | (&lt;span class="hilite variable"&gt;u&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="hilite number"&gt;12&lt;/span&gt;)&lt;span class="hilite delimiter"&gt;;&lt;/span&gt;
        &lt;span class="hilite keyword"&gt;return&lt;/span&gt;(&lt;span class="hilite function"&gt;bitcast&lt;/span&gt;(&lt;span class="hilite variable"&gt;double&lt;/span&gt;, &lt;span class="hilite variable"&gt;u&lt;/span&gt;) &lt;span class="hilite operator"&gt;-&lt;/span&gt; &lt;span class="hilite number"&gt;1.0&lt;/span&gt;)&lt;span class="hilite delimiter"&gt;;&lt;/span&gt;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It translates fairly directly to amd64 like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-asm"&gt;    &lt;span class="hilite label"&gt;bithack52&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;shr&lt;/span&gt;     &lt;span class="hilite variable builtin"&gt;rdi&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;12&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;movabs&lt;/span&gt;  &lt;span class="hilite variable builtin"&gt;rax&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;0x3ff0000000000000&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;or&lt;/span&gt;      &lt;span class="hilite variable builtin"&gt;rax&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;rdi&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;movq&lt;/span&gt;    &lt;span class="hilite variable builtin"&gt;xmm0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;rax&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;addsd&lt;/span&gt;   &lt;span class="hilite variable builtin"&gt;xmm0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite keyword"&gt;qword&lt;/span&gt; &lt;span class="hilite keyword"&gt;ptr&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;[&lt;/span&gt;&lt;span class="hilite variable builtin"&gt;rip&lt;/span&gt; &lt;span class="hilite operator"&gt;+&lt;/span&gt; .number&lt;span class="hilite punctuation bracket"&gt;]&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;ret&lt;/span&gt;
    .number&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;.quad&lt;/span&gt;   &lt;span class="hilite number"&gt;0xbff0000000000000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On arm64 the shift-and-or becomes one &lt;code&gt;bfxil&lt;/code&gt; instruction (which is a
kind of &lt;a href="https://developer.arm.com/documentation/ddi0602/2025-03/Base-Instructions/BFM--Bitfield-move-?lang=en"&gt;bitfield move&lt;/a&gt;), and the constant &lt;code&gt;-1.0&lt;/code&gt; is encoded more
briefly. Very neat!&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-asm"&gt;    &lt;span class="hilite label"&gt;bithack52&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;mov&lt;/span&gt;     &lt;span class="hilite variable builtin"&gt;x8&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#0x3ff0000000000000&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;fmov&lt;/span&gt;    &lt;span class="hilite variable builtin"&gt;d0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#-1&lt;/span&gt;.00000000
        &lt;span class="hilite function builtin"&gt;bfxil&lt;/span&gt;   &lt;span class="hilite variable builtin"&gt;x8&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;x0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#12&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#52&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;fmov&lt;/span&gt;    &lt;span class="hilite variable builtin"&gt;d1&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;x8&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;fadd&lt;/span&gt;    &lt;span class="hilite variable builtin"&gt;d0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;d1&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;d0&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#multiply" name="multiply"&gt;multiply&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The shift-convert-multiply function looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;    &lt;span class="hilite type"&gt;double&lt;/span&gt; &lt;span class="hilite function"&gt;multiply53&lt;/span&gt;(&lt;span class="hilite type"&gt;uint64_t&lt;/span&gt; &lt;span class="hilite variable"&gt;u&lt;/span&gt;) {
        &lt;span class="hilite keyword"&gt;return&lt;/span&gt; ((&lt;span class="hilite type"&gt;double&lt;/span&gt;)(&lt;span class="hilite variable"&gt;u&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="hilite number"&gt;11&lt;/span&gt;) &lt;span class="hilite operator"&gt;*&lt;/span&gt; &lt;span class="hilite number"&gt;0x1.0p-53&lt;/span&gt;)&lt;span class="hilite delimiter"&gt;;&lt;/span&gt;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It translates directly to amd64 like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-asm"&gt;    &lt;span class="hilite label"&gt;multiply53&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;shr&lt;/span&gt;       &lt;span class="hilite variable builtin"&gt;rdi&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;11&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;cvtsi2sd&lt;/span&gt;  &lt;span class="hilite variable builtin"&gt;xmm0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;rdi&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;mulsd&lt;/span&gt;     &lt;span class="hilite variable builtin"&gt;xmm0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite keyword"&gt;qword&lt;/span&gt; &lt;span class="hilite keyword"&gt;ptr&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;[&lt;/span&gt;&lt;span class="hilite variable builtin"&gt;rip&lt;/span&gt; &lt;span class="hilite operator"&gt;+&lt;/span&gt; .number&lt;span class="hilite punctuation bracket"&gt;]&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;ret&lt;/span&gt;
    .number&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;.quad&lt;/span&gt;     &lt;span class="hilite number"&gt;0x3ca0000000000000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GCC and earlier versions of Clang produce the following arm64 code,
which is similar though it requires more faff to get the constant into
the right register.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-asm"&gt;    &lt;span class="hilite label"&gt;multiply53&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;lsr&lt;/span&gt;     &lt;span class="hilite variable builtin"&gt;x8&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;x0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#11&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;mov&lt;/span&gt;     &lt;span class="hilite variable builtin"&gt;x9&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#0x3ca0000000000000&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;ucvtf&lt;/span&gt;   &lt;span class="hilite variable builtin"&gt;d0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;x8&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;fmov&lt;/span&gt;    &lt;span class="hilite variable builtin"&gt;d1&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;x9&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;fmul&lt;/span&gt;    &lt;span class="hilite variable builtin"&gt;d0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;d0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;d1&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recent versions of Clang produce this astonishingly brief two
instruction translation: apparently you can &lt;a href="https://developer.arm.com/documentation/ddi0602/2025-03/SIMD-FP-Instructions/UCVTF--scalar--fixed-point---Unsigned-fixed-point-convert-to-floating-point--scalar--?lang=en"&gt;convert fixed-point to
floating point&lt;/a&gt; in one instruction, which gives us the power of
two scale factor for free!&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-asm"&gt;    &lt;span class="hilite label"&gt;multiply53&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;lsr&lt;/span&gt;     &lt;span class="hilite variable builtin"&gt;x8&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;x0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#11&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;ucvtf&lt;/span&gt;   &lt;span class="hilite variable builtin"&gt;d0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite variable builtin"&gt;x8&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite number"&gt;#53&lt;/span&gt;
        &lt;span class="hilite function builtin"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#benchmark" name="benchmark"&gt;benchmark&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My benchmark has 2 x 2 x 2 tests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;bithacking vs multiplying&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;32 bit vs 64 bit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sequential integers vs random integers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ran the benchmark on my Apple M1 Pro and my AMD Ryzen 7950X.&lt;/p&gt;
&lt;p&gt;These functions are very small and work entirely in registers so it
has been tricky to measure them properly.&lt;/p&gt;
&lt;p&gt;To prevent the compiler from inlining and optimizing the benchmark
loop to nothing, the functions are compiled in a separate translation
unit from the test harness. This is not enough to get plausible
measurements because the CPU overlaps successive iterations of the
loop, so we also use fence instructions.&lt;/p&gt;
&lt;p&gt;On arm64, a single ISB (instruction stream barrier) in the loop is
enough to get reasonable measurements.&lt;/p&gt;
&lt;p&gt;I have not found an equivalent of ISB on amd64, so I’m using MFENCE.
It isn’t effective unless I pass the argument and return values via
pointers (because it’s a memory fence) and place MFENCE instructions
just before reading the argument and just after writing the result.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#results" name="results"&gt;results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the table below, the leftmost column is the number of random bits;
“old” is arm64 with older clang, “arm” is newer clang, “amd” is gcc.&lt;/p&gt;
&lt;p&gt;The first line is a baseline do-nothing function, showing the
overheads of the benchmark loop, function call, load argument, store
return, and fences.&lt;/p&gt;
&lt;p&gt;The upper half measures sequential numbers, the bottom half is random
numbers. The times are nanoseconds per operation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;         old    arm    amd

    00  21.44  21.41  21.42

    23  24.28  24.31  22.19
    24  25.24  24.31  22.94
    52  24.31  24.28  21.98
    53  25.32  24.35  22.25

    23  25.59  25.56  22.86
    24  26.55  25.55  23.03
    52  27.83  27.81  23.93
    53  28.57  27.84  25.01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The times vary a little from run to run but the difference in speed of
the various loops is reasonably consistent.&lt;/p&gt;
&lt;p&gt;The numbers on arm64 are reasonably plausible. The most notable thing
is that the “old” multiply conversion is about 3 or 4 clock cycles
slower, but with a newer compiler that can eliminate the multiply,
it’s the same speed as the bithacking conversion.&lt;/p&gt;
&lt;p&gt;On amd64 the multiply conversion is about 1 or 2 clock cycles slower
than the bithacking conversion.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-08-floats.html#conclusion" name="conclusion"&gt;conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The folklore says that bithacking floats is faster than normal integer
to float conversion, and my results generally agree with that, apart
from on arm64 with a good compiler. It would be interesting to compare
other CPUs to get a better idea of when the folklore is right or
wrong – or if any CPUs perform the other way round!&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Mon, 09 Jun 2025 19:50:27 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-06-08-floats.html</guid></item><item><title>A lesson on Trust but Verify and Managing Upward</title><link>https://boyter.org/posts/trust-but-verify-and-manage-upward/</link><description>&lt;p&gt;Previously I had covered the &lt;a href="https://boyter.org/2014/02/worst-program-worked/"&gt;worst program&lt;/a&gt; I had ever worked on (although that probably needs updating now) and the &lt;a href="https://boyter.org/2016/08/worst-individual-worked/"&gt;worst individual&lt;/a&gt; and &lt;a href="https://boyter.org/posts/most-incompetent-person-worked-with/"&gt;the most incompetent individual&lt;/a&gt; I ever worked with.&lt;/p&gt;
&lt;p&gt;I thought it was time to lay out one of my own failures, of which resulted in me adopting &lt;code&gt;trust by verify&lt;/code&gt; for a all future interactions&amp;hellip; at least until I am more familiar with the individual.&lt;/p&gt;
&lt;p&gt;This was back when I was working on C#, SQL Server and some ETL processing software the name of which I had forgotten. I had mentioned to my manager at the time that I should probably get more experience using the ETL software because simply because there was so much work there and I had never used it. When a new project came up to build a system for managing HR backend jobs, to deal with vanity and real titles and organizational chart management. Think, renaming titles, organizing organizational hierarchies and the like.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 09 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/trust-but-verify-and-manage-upward/</guid></item><item><title>Python regular expression cheatsheet and examples</title><link>https://learnbyexample.github.io/python-regex-cheatsheet/</link><description>&lt;p&gt;This blog post gives an overview and examples of regular expression syntax as implemented by the &lt;code&gt;re&lt;/code&gt; built-in module (Python 3.13+). Assume ASCII character set unless otherwise specified. This post is an excerpt from my &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; book.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Railroad visualization for the regular expression \bpar(en|ro)?t\b" src="/images/books/pyregex_example.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Visualization created using&lt;/em&gt; &lt;a href="https://www.debuggex.com"&gt;debuggex&lt;/a&gt; &lt;em&gt;for the pattern&lt;/em&gt; &lt;code&gt;r'\bpar(en|ro)?t\b'&lt;/code&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;p&gt;From &lt;a href="https://docs.python.org/3/library/re.html"&gt;docs.python: re&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A regular expression (or RE) specifies a set of strings that matches it; the functions in this module let you check if a particular string matches a given regular expression&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="elements-that-define-a-regular-expression"&gt;Elements that define a regular expression&lt;a class="zola-anchor" href="#elements-that-define-a-regular-expression"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Anchors&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\A&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the start of string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\Z&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the end of string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;^&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the start of line&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the end of line&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;newline character is used as the line separator&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.MULTILINE&lt;/code&gt; or &lt;code&gt;re.M&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to treat input as multiline string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\b&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the start/end of words&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;word characters: alphabets, digits, underscore&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\B&lt;/code&gt;&lt;/td&gt;&lt;td&gt;matches wherever &lt;code&gt;\b&lt;/code&gt; doesn't match&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;^&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt; are metacharacters in the above table, as these characters have special meaning. Prefix a &lt;code&gt;\&lt;/code&gt; character to remove the special meaning and match such characters literally. For example, &lt;code&gt;\^&lt;/code&gt; will match a &lt;code&gt;^&lt;/code&gt; character instead of acting as an anchor.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;|&lt;/code&gt;&lt;/td&gt;&lt;td&gt;multiple RE combined as conditional OR&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;each alternative can have independent anchors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;group patterns, also a capturing group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;a(b|c)d&lt;/code&gt; is same as &lt;code&gt;abd|acd&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?:pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;non-capturing group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?P&amp;lt;name&amp;gt;pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;named capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match any character except the newline character &lt;code&gt;\n&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Character class, matches one character among many&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Greedy Quantifiers&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match zero or more times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;+&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match one or more times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match zero or one times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{m,n}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match &lt;code&gt;m&lt;/code&gt; to &lt;code&gt;n&lt;/code&gt; times (inclusive)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{m,}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match at least &lt;code&gt;m&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{,n}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match up to &lt;code&gt;n&lt;/code&gt; times (including &lt;code&gt;0&lt;/code&gt; times)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{n}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match exactly &lt;code&gt;n&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1.*pat2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;any number of characters between &lt;code&gt;pat1&lt;/code&gt; and &lt;code&gt;pat2&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1.*pat2|pat2.*pat1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match both &lt;code&gt;pat1&lt;/code&gt; and &lt;code&gt;pat2&lt;/code&gt; in any order&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Greedy here means that the above quantifiers will match as much as possible that'll also honor the overall RE. Appending a &lt;code&gt;?&lt;/code&gt; to greedy quantifiers makes them &lt;strong&gt;non-greedy&lt;/strong&gt;, i.e. match as &lt;em&gt;minimally&lt;/em&gt; as possible. Appending a &lt;code&gt;+&lt;/code&gt; to greedy quantifiers makes them &lt;strong&gt;possessive&lt;/strong&gt;, which prevents backtracking. You can also use &lt;code&gt;(?&amp;gt;pat)&lt;/code&gt; &lt;strong&gt;atomic grouping&lt;/strong&gt; to safeguard from backtracking. Quantifiers can be applied to literal characters, groups, backreferences and character classes.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Character class&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[aeiou]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match any vowel&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[^aeiou]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;^&lt;/code&gt; inverts selection, so this matches any consonant&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[a-f]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;-&lt;/code&gt; defines a range, so this matches any of abcdef characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match a digit, same as &lt;code&gt;[0-9]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\D&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match non-digits, same as &lt;code&gt;[^0-9]&lt;/code&gt; or &lt;code&gt;[^\d]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\w&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match word characters, same as &lt;code&gt;[a-zA-Z0-9_]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\W&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match non-word characters, same as &lt;code&gt;[^a-zA-Z0-9_]&lt;/code&gt; or &lt;code&gt;[^\w]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\s&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match whitespace characters, same as &lt;code&gt;[\ \t\n\r\f\v]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\S&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Match non-whitespace characters, same as &lt;code&gt;[^\ \t\n\r\f\v]&lt;/code&gt; or &lt;code&gt;[^\s]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Lookarounds&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;lookarounds&lt;/td&gt;&lt;td&gt;custom assertions, zero-width like anchors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?!pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negative lookahead assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;!pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negative lookbehind assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?=pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;positive lookahead assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;=pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;positive lookbehind assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?!pat1)(?=pat2)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;multiple assertions can be specified in any order&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;as they mark a matching location without consuming characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;((?!pat).)*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Negate a grouping, similar to negated character class&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Flags&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.IGNORECASE&lt;/code&gt; or &lt;code&gt;re.I&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to ignore case&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.DOTALL&lt;/code&gt; or &lt;code&gt;re.S&lt;/code&gt;&lt;/td&gt;&lt;td&gt;allow &lt;code&gt;.&lt;/code&gt; metacharacter to match newline characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;flags=re.S|re.I&lt;/code&gt;&lt;/td&gt;&lt;td&gt;multiple flags can be combined using &lt;code&gt;|&lt;/code&gt; operator&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.MULTILINE&lt;/code&gt; or &lt;code&gt;re.M&lt;/code&gt;&lt;/td&gt;&lt;td&gt;allow &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; anchors to match line wise&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.VERBOSE&lt;/code&gt; or &lt;code&gt;re.X&lt;/code&gt;&lt;/td&gt;&lt;td&gt;allows to use literal whitespaces for aligning purposes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;and to add comments after the &lt;code&gt;#&lt;/code&gt; character&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;escape spaces and &lt;code&gt;#&lt;/code&gt; if needed as part of actual RE&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.ASCII&lt;/code&gt; or &lt;code&gt;re.A&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match only ASCII characters for &lt;code&gt;\b&lt;/code&gt;, &lt;code&gt;\w&lt;/code&gt;, &lt;code&gt;\d&lt;/code&gt;, &lt;code&gt;\s&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;and their opposites, applicable only for Unicode patterns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.LOCALE&lt;/code&gt; or &lt;code&gt;re.L&lt;/code&gt;&lt;/td&gt;&lt;td&gt;use locale settings for byte patterns and 8-bit locales&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?#comment)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;another way to add comments (not a flag)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?flags:pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;inline flags only for this &lt;code&gt;pat&lt;/code&gt;, overrides &lt;code&gt;flags&lt;/code&gt; argument&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;flags is &lt;code&gt;i&lt;/code&gt; for &lt;code&gt;re.I&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt; for &lt;code&gt;re.S&lt;/code&gt;, etc, except &lt;code&gt;L&lt;/code&gt; for &lt;code&gt;re.L&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?-flags:pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negate flags only for this &lt;code&gt;pat&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?flags-flags:pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;apply and negate particular flags only for this &lt;code&gt;pat&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?flags)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;apply flags for whole RE, can be used only at start of RE&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;anchors if any, should be specified after &lt;code&gt;(?flags)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Matched portion&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.Match&lt;/code&gt; object&lt;/td&gt;&lt;td&gt;details like matched portions, location, etc&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m[0]&lt;/code&gt; or &lt;code&gt;m.group(0)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;entire matched portion of &lt;code&gt;re.Match&lt;/code&gt; object &lt;code&gt;m&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m[n]&lt;/code&gt; or &lt;code&gt;m.group(n)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;matched portion of the &lt;em&gt;n&lt;/em&gt;th capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m.groups()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;tuple of all the capture groups' matched portions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m.span()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;start and end+1 index of the entire matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;pass a number to get span of that particular capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;can also use &lt;code&gt;m.start()&lt;/code&gt; and &lt;code&gt;m.end()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\N&lt;/code&gt;&lt;/td&gt;&lt;td&gt;backreference, gives matched portion of the &lt;em&gt;N&lt;/em&gt;th capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;applies to both search and replacement sections&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;possible values: &lt;code&gt;\1&lt;/code&gt;, &lt;code&gt;\2&lt;/code&gt; up to &lt;code&gt;\99&lt;/code&gt; provided no more digits&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\g&amp;lt;N&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;backreference, gives matched portion of the Nth capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;possible values: &lt;code&gt;\g&amp;lt;0&amp;gt;&lt;/code&gt;, &lt;code&gt;\g&amp;lt;1&amp;gt;&lt;/code&gt;, etc (not limited to 99)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;\g&amp;lt;0&amp;gt;&lt;/code&gt; refers to the entire matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?P&amp;lt;name&amp;gt;pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;named capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;refer as &lt;code&gt;'name'&lt;/code&gt; in &lt;code&gt;re.Match&lt;/code&gt; object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;refer as &lt;code&gt;(?P=name)&lt;/code&gt; in search section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;refer as &lt;code&gt;\g&amp;lt;name&amp;gt;&lt;/code&gt; in replacement section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;groupdict&lt;/code&gt;&lt;/td&gt;&lt;td&gt;method applied on a &lt;code&gt;re.Match&lt;/code&gt; object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;gives named capture group portions as a &lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; &lt;code&gt;\0&lt;/code&gt; and &lt;code&gt;\100&lt;/code&gt; onwards are considered as octal values, hence cannot be used as backreferences.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="re-module-functions"&gt;re module functions&lt;a class="zola-anchor" href="#re-module-functions"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Function&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.search&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Check if given pattern is present anywhere in input string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Output is a &lt;code&gt;re.Match&lt;/code&gt; object, usable in conditional expressions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;r-strings preferred to define RE&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Use byte pattern for byte input&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Python also maintains a small cache of recent RE&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.fullmatch&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ensures pattern matches the entire input string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.compile&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Compile a pattern for reuse, outputs &lt;code&gt;re.Pattern&lt;/code&gt; object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.sub&lt;/code&gt;&lt;/td&gt;&lt;td&gt;search and replace&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.sub(r'pat', f, s)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;function &lt;code&gt;f&lt;/code&gt; with &lt;code&gt;re.Match&lt;/code&gt; object as the argument&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.escape&lt;/code&gt;&lt;/td&gt;&lt;td&gt;automatically escape all metacharacters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.split&lt;/code&gt;&lt;/td&gt;&lt;td&gt;split a string based on RE&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;text matched by the groups will be part of the output&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;portion matched by pattern outside group won't be in output&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.findall&lt;/code&gt;&lt;/td&gt;&lt;td&gt;returns all the matches as a list&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;if 1 capture group is used, only its matches are returned&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;1+, each element will be tuple of capture groups&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;portion matched by pattern outside group won't be in output&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.finditer&lt;/code&gt;&lt;/td&gt;&lt;td&gt;iterator with &lt;code&gt;re.Match&lt;/code&gt; object for each match&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;re.subn&lt;/code&gt;&lt;/td&gt;&lt;td&gt;gives tuple of modified string and number of substitutions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The function definitions are given below:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(pattern, string, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;fullmatch&lt;/span&gt;&lt;span&gt;(pattern, string, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;compile&lt;/span&gt;&lt;span&gt;(pattern, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(pattern, repl, string, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;count&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;escape&lt;/span&gt;&lt;span&gt;(pattern)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(pattern, string, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;maxsplit&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(pattern, string, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;finditer&lt;/span&gt;&lt;span&gt;(pattern, string, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;subn&lt;/span&gt;&lt;span&gt;(pattern, repl, string, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;count&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="regular-expression-examples"&gt;Regular expression examples&lt;a class="zola-anchor" href="#regular-expression-examples"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a good practice, always use &lt;strong&gt;raw strings&lt;/strong&gt; to construct RE, unless other formats are required. This will avoid conflict between special meaning of the backslash character in RE and string literals.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; I wrote an interactive TUI app to help you experiment with the examples presented below. See &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexPlayground"&gt;PyRegexPlayground&lt;/a&gt; repo for installation instructions and usage guide. See &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexExercises"&gt;PyRegexExercises&lt;/a&gt; repo for a TUI app with 100+ Python regex exercises.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;examples for &lt;code&gt;re.search()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;sentence &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'This is a sample string'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# need to load the re module before use
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; import &lt;/span&gt;&lt;span&gt;re
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# check if 'sentence' contains the pattern described by RE argument
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;is&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, sentence))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# ignore case while searching for a match
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;this&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, sentence, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;re.I))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# example for a pattern not found in the input string
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;xyz&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, sentence))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;False
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# re.search output can be directly used in conditional expressions
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;ring&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, sentence):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'mission success'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span&gt;mission success
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use raw byte strings for patterns if input is of byte data type
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;rb&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;is&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'This is a sample string'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;string and line anchors&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# match the start of the input string
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\A&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;hi&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hi hello&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;top spot'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match the start of a line
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;top&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hi hello&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;top spot'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;re.M))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match the end of strings
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'surrender'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'up'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'newer'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'do'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'era'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'eel'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'pest'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[w &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;w &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;er&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\Z&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, w)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'surrender'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'newer'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# check if there's a whole line 'par'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;par&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;$&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'spare&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;par&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;dare'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;re.M))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for &lt;code&gt;re.findall()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# match 'par' with optional 's' at start and optional 'e' at end
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;pare&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spar apparent spare part pare'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'spar'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'spare'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'pare'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# numbers &amp;gt;= 100 with optional leading zeros
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# you'd need r'\b0*[1-9]\d{2,}\b' if possessive quantifiers isn't used
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'0501 035 154 12 26 98234'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'0501'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'154'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'98234'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# if multiple capturing groups are used, each element of output
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# will be a tuple of strings of all the capture groups
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;/]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)/(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;/,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;),&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2020/04,1986/Mar'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2020'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'04'&lt;/span&gt;&lt;span&gt;), (&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1986'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mar'&lt;/span&gt;&lt;span&gt;)]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# normal capture group will hinder ability to get the whole match
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# non-capturing group to the rescue
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(?:st&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;in)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cost akin more east run'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cost'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'akin'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'east'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# useful for debugging purposes as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'green:3.14:teal::brown:oh!:blue'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':3.14:'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':oh!:'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for &lt;code&gt;re.split()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# split based on one or more digit characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample123string42with777numbers'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'string'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'with'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'numbers'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# split based on digit or whitespace characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\d\s&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;3star&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;7 77&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;**'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'star'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# to include the matching delimiter strings as well in the output
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample123string42with777numbers'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'string'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'with'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'777'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'numbers'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# multiple capture groups example
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that the portion matched by b+ isn't present in the output
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)b&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(c&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'3.14aabccc42'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'3.14'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ccc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use non-capturing group if capturing is not needed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;hand(?:y&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;ful)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123handed42handy777handful500'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123handed42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'777'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'500'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;backreferencing within the search pattern&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'effort'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'flee'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'facade'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'oddball'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'rat'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tool'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# whole words that have at least one consecutive repeated character
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[w &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;w &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, w)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'effort'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'flee'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'oddball'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tool'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;working with matched portions&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# re.Match object
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'too soon a song snatch'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;re.Match &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;object&lt;/span&gt;&lt;span&gt;; span&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;8&lt;/span&gt;&lt;span&gt;), match&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'soon'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# retrieving the entire matched portion, note the use of [0]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;motivation &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Doing is often better than thinking of doing.'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;of&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;ink&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, motivation)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'often better than think'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# capture group example
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;purchase &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'coffee:100g tea:250g sugar:75g chocolate:50g'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)g&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)g&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;chocolate:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)g&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, purchase)
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# to get the matched portion of the second capture group
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;m[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'250'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# to get a tuple of all the capture groups
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;m.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;groups&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'100'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'250'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'50'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for &lt;code&gt;re.finditer()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# numbers &amp;lt; 350
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;m_iter &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;finditer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'45 349 651 593 4 204 350'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[m[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;m_iter &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;int&lt;/span&gt;&lt;span&gt;(m[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;350&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'45'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'349'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'4'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'204'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# start and end+1 index of each matching portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;m_iter &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;finditer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'song too soon snatch'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; for &lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;m_iter:
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(m.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;span&lt;/span&gt;&lt;span&gt;())
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;9&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;13&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for &lt;code&gt;re.sub()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# add something to the start of every line
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip_lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;catapults&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;concatenate&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;cat&amp;quot;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'* '&lt;/span&gt;&lt;span&gt;, ip_lines, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;re.M))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;catapults
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;concatenate
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;cat
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace 'par' only at the start of a word
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;par&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spar apparent spare part'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X spar apparent spare Xt'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: r'part|parrot|parent'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;par(en&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;ro)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par part parrot parent'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par X X X'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# remove first two columns where : is delimiter
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\A&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;:]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{2}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple:123:banana:cherry'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'banana:cherry'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;backreferencing in the replacement section&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# remove any number of consecutive duplicate words separated by space
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use \W+ instead of space to cover cases like 'a;a&amp;lt;-;a'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)( &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa a a a 42 f_1 f_1 f_13.14'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa a 42 f_1 f_13.14'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# add something around the matched strings
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;0)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'52 apples and 31 mangoes'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(520) apples and (310) mangoes'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# swap words that are separated by a comma
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;),(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\2&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'good,bad 42,24'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'bad,good 24,42'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# example with both capturing and non-capturing groups
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)(?:abc)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\2&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1000abcabc42 12abcd21'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42:1000 12abcd21'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;using functions in the replacement section of &lt;code&gt;re.sub()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; from &lt;/span&gt;&lt;span&gt;math &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;import &lt;/span&gt;&lt;span&gt;factorial
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1 2 3 4 5'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="background-color: #562d56bf; color: #f8f8f8;"&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;fact_num&lt;/span&gt;&lt;span&gt;(n):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;str&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;factorial&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;int&lt;/span&gt;&lt;span&gt;(n[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;])))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, fact_num, numbers)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1 2 6 24 120'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# using lambda
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;lambda &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;str&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;factorial&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;int&lt;/span&gt;&lt;span&gt;(m[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]))), numbers)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1 2 6 24 120'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for lookarounds&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# change 'cat' only if it is not followed by a digit character
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that the end of string satisfies the given assertion
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 'catcat' has two matches as the assertion doesn't consume characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;cat(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?!\d&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey cats! cat42 cat_5 catcat'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey dogs! cat42 dog_5 dogdog'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# change whole word only if it is not preceded by : or -
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;![:-]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart &amp;lt;apple -rest ;tea'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart &amp;lt;X -rest ;X'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract digits only if it is preceded by - and followed by ; or :
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;-)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=[:;]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 apple-5, fig3; x-83, y-20: f12'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'20'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# words containing 'b' and 'e' and 't' in any order
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sequoia'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'questionable'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'exhibit'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'equation'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[w &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;w &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;b)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;e)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, w)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'questionable'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'exhibit'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match if 'do' is not there between 'at' and 'par'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;at((&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?!&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;do)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;par&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox,cat,dog,parrot'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;False
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match if 'go' is not there between 'at' and 'par'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;at((&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?!&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;go)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;par&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox,cat,dog,parrot'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for &lt;code&gt;re.compile()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regular expressions can be compiled using the &lt;code&gt;re.compile()&lt;/code&gt; function, which gives back a &lt;code&gt;re.Pattern&lt;/code&gt; object. The top level &lt;code&gt;re&lt;/code&gt; module functions are all available as methods for this object. Compiling a regular expression helps if the RE has to be used in multiple places or called upon multiple times inside a loop (speed benefit). By default, Python maintains a small list of recently used RE, so the speed benefit doesn't apply for trivial use cases.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;pet &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;compile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;dog&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;type&lt;/span&gt;&lt;span&gt;(pet)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background-color: #562d56bf; color: #f8f8f8;"&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'re.Pattern'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(pet.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'They bought a dog'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(pet.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'A cat crossed their path'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;False
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;pat &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;compile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\([&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;)]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;pat.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a+b(addition) - foo() + c&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%d&lt;/span&gt;&lt;span style="color: #d07711;"&gt;(#modulo)'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a+b - foo + c&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%d&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;pat.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi there(greeting). Nice day(a(b)'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi there. Nice day'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="understanding-python-re-gex-book"&gt;Understanding Python re(gex)? book&lt;a class="zola-anchor" href="#understanding-python-re-gex-book"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit my GitHub repo &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; for details about the book I wrote on Python regular expressions. The book uses plenty of examples to explain the concepts from the basics and introduces more advanced concepts step-by-step. The book also covers the &lt;a href="https://pypi.org/project/regex/"&gt;third-party regex module&lt;/a&gt;. The cheatsheet and examples presented in this post are based on the contents of this book.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Understanding Python re(gex)? cover image" height="360px" src="https://raw.githubusercontent.com/learnbyexample/py_regular_expressions/master/images/py_regex_ls.png" width="640px" /&gt;&lt;/p&gt;
&lt;p&gt;You can get all my ebooks as a single bundle via &lt;a href="https://leanpub.com/b/learnbyexample-all-books"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books"&gt;gumroad&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 09 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/python-regex-cheatsheet/</guid></item><item><title>Hating Trump More Won't Make Things Better</title><link>https://blog.rongarret.info/2025/06/hating-trump-more-wont-make-things.html</link><description>It has been nearly five months now since I published my open letter to Democratic candidates and organizations.&amp;nbsp; Since then I have, unsurprisingly, received dozens of texts and emails asking me to "Donate $5 now!"&amp;nbsp; For a while I responded to every one pointing them to my Open Letter and asking them to read it.&amp;nbsp; I was expecting (hoping for?) one of three responses.&amp;nbsp; 1) "You are</description><author>Rondam Ramblings</author><pubDate>Mon, 09 Jun 2025 02:49:16 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/06/hating-trump-more-wont-make-things.html</guid></item><item><title>Vibe Coding vs Agentic Coding</title><link>https://smcleod.net/2025/06/vibe-coding-vs-agentic-coding/</link><description>From Creative Exploration to Production Quality</description><author>smcleod.net</author><pubDate>Thu, 05 Jun 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/06/vibe-coding-vs-agentic-coding/</guid></item><item><title>Mobile blogging, the past and the future</title><link>https://bergie.iki.fi/blog/mobile-blogging/</link><description>&lt;p&gt;This blog has been running more or less continuously since mid-nineties. The site has existed in multiple forms, and with different ways to publish. But what’s common is that at almost all points there was a mechanism to publish while on the move.&lt;/p&gt;

&lt;h2 id="psion-documents-over-ftp"&gt;Psion, documents over FTP&lt;/h2&gt;

&lt;p&gt;In the early 2000s we were into adventure motorcycling. To be able to share our adventures, we implemented a way to publish blogs while on the go. The device that enabled this was the &lt;a href="https://en.wikipedia.org/wiki/Psion_Series_5"&gt;Psion Series 5&lt;/a&gt;, a handheld computer that was very much a device ahead of its time.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Psion S5, also known as the Ancestor" src="https://d2vqpl3tx84ay5.cloudfront.net/psions5.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;The Psion had a reasonably sized keyboard and a good native word processing app. And battery life good for weeks of usage. Writing while underway was easy. The Psion could use a mobile phone as a modem over an infrared connection, and with that we could upload the documents to a server over FTP.&lt;/p&gt;

&lt;p&gt;Server-side, a cron job would grab the new documents, converting them to HTML and adding them to our CMS.&lt;/p&gt;

&lt;p&gt;In the early days of GPRS, getting this to work while roaming was quite tricky. But the system served us well for years.&lt;/p&gt;

&lt;p&gt;If we wanted to include photos to the stories, we’d have to find an Internet cafe.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://bergie.iki.fi/blog/to-to-alps/"&gt;To the Alps&lt;/a&gt; is a post from these times. Lots more in the &lt;a href="http://bergie.iki.fi/blog/category/motorcycles/"&gt;motorcycling category&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="sms-and-mms"&gt;SMS and MMS&lt;/h2&gt;

&lt;p&gt;For an even more mobile setup, I implemented an SMS-based blogging system. We had an old phone connected to a computer back in the office, and I could write to my blog by simply sending a text. These would automatically end up as a new paragraph in the latest post. If I started the text with &lt;code class="language-plaintext highlighter-rouge"&gt;NEWPOST&lt;/code&gt;, an empty blog post would be created with the rest of that message’s text as the title.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://bergie.iki.fi/blog/in-the-caucasus/"&gt;In the Caucasus&lt;/a&gt; is a good example of a post from this era&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As I got into &lt;a href="http://bergie.iki.fi/blog/category/geo/"&gt;neogeography&lt;/a&gt;, I could also send a &lt;code class="language-plaintext highlighter-rouge"&gt;NEWPOSITION&lt;/code&gt; message. This would update my position on the map, connecting weather metadata to the posts.&lt;/p&gt;

&lt;p&gt;As camera phones became available, we wanted to do pictures too. For the Death Monkey rally where we rode minimotorcycles from Helsinki to Gibraltar, we implemented an MMS-based system. With that the entries could include both text and pictures. But for that you needed a gateway, which was really only realistic for an event with sponsors.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://web.archive.org/web/20061013183009/http://www.deathmonkey.org/view/mystery-of-the-missing-monkey.html"&gt;Mystery of the Missing Monkey&lt;/a&gt; is typical. Some more in &lt;a href="https://web.archive.org/web/20060804205237/http://www.deathmonkey.org/"&gt;Internet Archive&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="photos-over-email"&gt;Photos over email&lt;/h2&gt;

&lt;p&gt;A much easier setup than MMS was to slightly come back to the old Psion setup, but instead of word documents, sending email with picture attachments. This was something that the new breed of (pre-iPhone) smartphones were capable of. And by now the roaming question was mostly sorted.&lt;/p&gt;

&lt;p&gt;And so my blog included a new “moblog” section. This is where I could share my daily activities as poor-quality pictures. Sort of how people would use Instagram a few years later.&lt;/p&gt;

&lt;p&gt;&lt;img alt="My blog from that era" src="https://d2vqpl3tx84ay5.cloudfront.net/bergie_layout_2006.jpg" /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://web.archive.org/web/20110604011733/http://bergie.iki.fi/moblog"&gt;Internet Archive has some of my old moblogs&lt;/a&gt; but nowadays, I post similar stuff &lt;a href="https://pixelfed.de/bergie"&gt;on Pixelfed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="pause"&gt;Pause&lt;/h2&gt;

&lt;p&gt;Then there was sort of a long pause in mobile blogging advancements. Modern smartphones, data roaming, and WiFi hotspots had become ubiquitous.&lt;/p&gt;

&lt;p&gt;In the meanwhile the blog also got &lt;a href="http://bergie.iki.fi/blog/blog-2012-edition/"&gt;migrated to a Jekyll-based system&lt;/a&gt; hosted on AWS. That means the old Midgard-based integrations were off the table.&lt;/p&gt;

&lt;p&gt;And I traveled off-the-grid rarely enough that it didn’t make sense to develop a system.&lt;/p&gt;

&lt;p&gt;But now that we’re &lt;a href="https://lille-oe.de"&gt;sailing offshore&lt;/a&gt;, that has changed. Time for new systems and new ideas. Or maybe just a rehash of the old ones?&lt;/p&gt;

&lt;h2 id="starlink-internet-from-outer-space"&gt;Starlink, Internet from Outer Space&lt;/h2&gt;

&lt;p&gt;Most cruising boats - ours included - now run the Starlink satellite broadband system. This enables full Internet, even in the middle of an ocean, even video calls! With this, we can use normal blogging tools. The usual one for us is &lt;a href="https://gitjournal.io"&gt;GitJournal&lt;/a&gt;, which makes it easy to write Jekyll-style Markdown posts and push them to GitHub.&lt;/p&gt;

&lt;p&gt;However, Starlink is a complicated, energy-hungry, and fragile system on an offshore boat. The policies might change at any time preventing our way of using it, and also the dishy itself, or the way we power it may fail.&lt;/p&gt;

&lt;p&gt;But despite what you’d think, even on a nerdy boat like ours, loss of Internet connectivity is not an emergency. And this is where the old-style mobile blogging mechanisms come handy.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Any of the &lt;a href="https://lille-oe.de/2025/"&gt;2025 Atlantic crossing posts&lt;/a&gt; is a good example of this setup in action&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="inreach-texting-with-the-cloud"&gt;Inreach, texting with the cloud&lt;/h2&gt;

&lt;p&gt;Our backup system to Starlink is the Garmin Inreach. This is a tiny battery-powered device that connects to the Iridium satellite constellation. It allows tracking as well as basic text messaging.&lt;/p&gt;

&lt;p&gt;When we head offshore we always enable tracking on the Inreach. This allows both our blog and our friends ashore to follow our progress.&lt;/p&gt;

&lt;p&gt;I also made a simple integration where text updates sent to &lt;a href="https://share.garmin.com/home"&gt;Garmin MapShare&lt;/a&gt; get fetched and published on our blog. Right now this is just plain text-based entries, but one could easily implement a command system similar to what I had over SMS back in the day.&lt;/p&gt;

&lt;p&gt;One benefit of the Inreach is that we can also take it with us when we go on land adventures. And it’d even enable rudimentary communications if we found ourselves in a liferaft.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;There are &lt;a href="https://github.com/tabeaeggler/MarineGRIB-InReach-Transmitter"&gt;various InReach integration hacks&lt;/a&gt; that could be used for more sophisticated data transfer&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="sailmail-and-email-over-hf-radio"&gt;Sailmail and email over HF radio&lt;/h2&gt;

&lt;p&gt;The other potential backup for Starlink failures would be to go seriously old-school. It is possible to get email access via a SSB radio and a Pactor (or &lt;a href="https://rosmodem.wordpress.com"&gt;Vara&lt;/a&gt;) modem.&lt;/p&gt;

&lt;p&gt;Our boat is already equipped with an isolated aft stay that can be used as an antenna. And with the popularity of Starlink, many cruisers are offloading their old HF radios.&lt;/p&gt;

&lt;p&gt;Licensing-wise this system could be used either as a marine HF radio (requiring a Long Range Certificate), or amateur radio. So that part is something I need to work on. Thankfully post-COVID, radio amateur license exams can be done online.&lt;/p&gt;

&lt;p&gt;With this setup we could send and receive text-based email. The &lt;a href="https://sailmail.com"&gt;Airmail&lt;/a&gt; application used for this can even do some automatic templating for position reports. We’d then need a mailbox that can receive these mails, and some automation to fetch and publish.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.sailblogs.com/wiki/index.php/Using_SailBlogs_Remote"&gt;Sailmail&lt;/a&gt; and &lt;a href="https://www.noforeignland.com/help/boat/move-email"&gt;No Foreign Land&lt;/a&gt; support structured data via email to update position. Their formats could be useful inspiration&lt;/li&gt;
&lt;/ul&gt;</description><author>Henri Bergius</author><pubDate>Thu, 05 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://bergie.iki.fi/blog/mobile-blogging/</guid></item><item><title>Mapping latitude and longitude to country, state, or city</title><link>https://austinhenley.com/blog/coord2state.html</link><description>&lt;a href="https://austinhenley.com/blog/coord2state.html"&gt;https://austinhenley.com/blog/coord2state.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Wed, 04 Jun 2025 03:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/coord2state.html</guid></item><item><title>Customizing pandoc to generate beautiful pdf and epub from markdown</title><link>https://learnbyexample.github.io/customizing-pandoc/</link><description>&lt;p&gt;Either you've already heard of &lt;code&gt;pandoc&lt;/code&gt; or if you have searched online for &lt;code&gt;markdown&lt;/code&gt; to &lt;code&gt;pdf&lt;/code&gt; or similar, you are sure to come across &lt;code&gt;pandoc&lt;/code&gt;. This tutorial will help you use &lt;code&gt;pandoc&lt;/code&gt; to generate &lt;code&gt;pdf&lt;/code&gt; and &lt;code&gt;epub&lt;/code&gt; versions from a &lt;a href="https://github.github.com/gfm/"&gt;GitHub style markdown&lt;/a&gt; file.&lt;/p&gt;
&lt;p&gt;The main motivation for this blog post is to highlight the customizations I used for &lt;a href="https://learnbyexample.github.io/books/"&gt;self-publishing my ebooks&lt;/a&gt;. It wasn't easy to arrive at the setup I ended up with, so I hope this will be useful for those looking to use &lt;code&gt;pandoc&lt;/code&gt; for such a purpose. This guide is specifically aimed at technical books that has code snippets.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Customizing pandoc" src="/images/pandoc_pdf/customizing_pandoc.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Poster created using &lt;a href="https://www.canva.com/"&gt;Canva&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you use a debian based distro like Ubuntu, the below steps are enough for the demos in this tutorial. If you get an error or warning, search that issue online and you'll likely find what else has to be installed.&lt;/p&gt;
&lt;p&gt;I first downloaded &lt;code&gt;deb&lt;/code&gt; file from &lt;a href="https://github.com/jgm/pandoc/releases"&gt;pandoc: releases&lt;/a&gt; and installed it. Followed by packages needed for &lt;code&gt;pdf&lt;/code&gt; generation.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# latest pandoc version as of 19 May 2025
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; sudo gdebi &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;~&lt;/span&gt;&lt;span&gt;/Downloads/pandoc-3.7.0.1-1-amd64.deb
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that download size is hundreds of MB
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; sudo apt install texlive-xetex
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; sudo apt install librsvg2-bin
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; sudo apt install texlive-science
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more details and instructions for other operating systems, refer to &lt;a href="https://pandoc.org/installing.html"&gt;pandoc: installation&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="minimal-example"&gt;Minimal example&lt;a class="zola-anchor" href="#minimal-example"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once &lt;code&gt;pandoc&lt;/code&gt; is working on your system, try generating a sample &lt;code&gt;pdf&lt;/code&gt; without any customization.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://github.com/learnbyexample/learnbyexample.github.io/tree/master/files/pandoc_pdf"&gt;learnbyexample.github.io repo&lt;/a&gt; for all the input and output files referred in this tutorial.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pandoc sample_1.md&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -f&lt;/span&gt;&lt;span&gt; gfm&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -o&lt;/span&gt;&lt;span&gt; sample_1.pdf
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here &lt;code&gt;sample_1.md&lt;/code&gt; is input markdown file and &lt;code&gt;-f&lt;/code&gt; is used to specify that the input format is GitHub style markdown. The &lt;code&gt;-o&lt;/code&gt; option specifies the output file type based on extension. The default output is probably good enough. But I wished to customize hyperlinks, inline code style, add page breaks between chapters, etc. This blog post will discuss these customizations one by one.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; &lt;code&gt;pandoc&lt;/code&gt; has its own flavor of &lt;code&gt;markdown&lt;/code&gt; with many useful extensions — see &lt;a href="https://pandoc.org/MANUAL.html#pandocs-markdown"&gt;pandoc: pandocs-markdown&lt;/a&gt; for details. GitHub style markdown is recommended if you wish to use the same source (or with minor changes) in multiple places.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; It is advised to use &lt;code&gt;markdown&lt;/code&gt; headers in order without skipping — for example, &lt;code&gt;H1&lt;/code&gt; for chapter heading and &lt;code&gt;H2&lt;/code&gt; for chapter sub-section, etc is fine. &lt;code&gt;H1&lt;/code&gt; for chapter heading and &lt;code&gt;H3&lt;/code&gt; for sub-section is not. Using the former can give automatic index navigation on ebook readers.&lt;/p&gt;
&lt;p&gt;On &lt;a href="https://wiki.gnome.org/Apps/Evince"&gt;Evince&lt;/a&gt; reader, the index navigation for above sample looks like this:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="index navigation" src="/images/pandoc_pdf/chapter_index.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="chapter-breaks"&gt;Chapter breaks&lt;a class="zola-anchor" href="#chapter-breaks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As observed from the previous demo, there are no chapter breaks by default. Searching for a &lt;a href="https://superuser.com/questions/601469/getting-chapters-to-start-on-a-new-page-in-a-pandoc-generated-pdf"&gt;solution online&lt;/a&gt;, I got this piece of &lt;code&gt;tex&lt;/code&gt; code:&lt;/p&gt;
&lt;pre class="language-latex " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-latex"&gt;&lt;span style="color: #72ab00;"&gt;\usepackage&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;sectsty&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\sectionfont&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\clearpage&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be added using the &lt;code&gt;-H&lt;/code&gt; option. From &lt;code&gt;pandoc&lt;/code&gt; manual:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;-H FILE, --include-in-header=FILE&lt;/p&gt;
&lt;p&gt;Include  contents  of FILE, verbatim, at the end of the header.  This
can be used, for example, to include special  CSS  or  JavaScript  in
HTML documents.  This option can be used repeatedly to include multiple
files in the header.  They will be included in the  order  specified.
Implies --standalone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;pandoc&lt;/code&gt; invocation now looks like:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pandoc sample_1.md&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -f&lt;/span&gt;&lt;span&gt; gfm&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -H&lt;/span&gt;&lt;span&gt; chapter_break.tex&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -o&lt;/span&gt;&lt;span&gt; sample_1_chapter_break.pdf
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can add further customization to headings, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\sectionfont{\underline\clearpage}&lt;/code&gt; to underline chapter names&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\sectionfont{\LARGE\clearpage}&lt;/code&gt; to allow chapter names to get even bigger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some more links to read about various customizations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tex.stackexchange.com/questions/1455/how-to-set-the-font-for-a-section-title-and-chapter-etc"&gt;tex.stackexchange: section fonts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tex.stackexchange.com/questions/230730/section-coming-up-as-undefined-when-using-sectsty"&gt;tex.stackexchange: section colors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tex.stackexchange.com/questions/10138/change-section-fonts"&gt;tex.stackexchange: change section fonts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="changing-settings-via-v-option"&gt;Changing settings via -V option&lt;a class="zola-anchor" href="#changing-settings-via-v-option"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;-V KEY[=VAL], --variable=KEY[:VAL]&lt;/p&gt;
&lt;p&gt;Set the template variable KEY to the value  VAL  when  rendering  the
document  in standalone mode.  This is generally only useful when the
--template option is used to specify a custom template, since  pandoc
automatically  sets  the variables used in the default templates.  If
no VAL is specified, the key will be given the value true.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;-V&lt;/code&gt; option allows to change variable values to customize settings like page size, font, link color, etc. As more settings are changed, better to use a simple script to call &lt;code&gt;pandoc&lt;/code&gt; instead of typing the whole command on the terminal.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;#!/bin/bash
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;pandoc &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;1&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    -f&lt;/span&gt;&lt;span&gt; gfm \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    --include-in-header&lt;/span&gt;&lt;span&gt; chapter_break.tex \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    -V&lt;/span&gt;&lt;span&gt; linkcolor:blue \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    -V&lt;/span&gt;&lt;span&gt; geometry:a4paper \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    -V&lt;/span&gt;&lt;span&gt; geometry:margin=2cm \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    -V&lt;/span&gt;&lt;span&gt; mainfont=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;DejaVu Serif&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    -V&lt;/span&gt;&lt;span&gt; monofont=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;DejaVu Sans Mono&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    --pdf-engine&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;xelatex \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;    -o &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;2&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mainfont&lt;/code&gt; is for normal text&lt;/li&gt;
&lt;li&gt;&lt;code&gt;monofont&lt;/code&gt; is for code snippets&lt;/li&gt;
&lt;li&gt;&lt;code&gt;geometry&lt;/code&gt; is for page size and margins&lt;/li&gt;
&lt;li&gt;&lt;code&gt;linkcolor&lt;/code&gt; will set the color for internal links
&lt;ul&gt;
&lt;li&gt;this will also colorize other types of links&lt;/li&gt;
&lt;li&gt;set &lt;code&gt;urlcolor&lt;/code&gt; if you want to distinguish URLs and so on for other types&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;to increase the default &lt;strong&gt;font size&lt;/strong&gt;, use &lt;code&gt;-V fontsize=12pt&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;See &lt;a href="https://stackoverflow.com/q/23811002/4082052"&gt;stackoverflow: change font size&lt;/a&gt; if you need even bigger size options&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using &lt;code&gt;xelatex&lt;/code&gt; as the &lt;code&gt;pdf-engine&lt;/code&gt; helps to use any font installed in your system. One reason I chose &lt;code&gt;DejaVu&lt;/code&gt; was because it supported &lt;strong&gt;Greek&lt;/strong&gt; and other Unicode characters that were causing error with other fonts. See &lt;a href="https://tex.stackexchange.com/questions/21736/using-xelatex-instead-of-pdflatex"&gt;tex.stackexchange: Using XeLaTeX instead of pdfLaTeX&lt;/a&gt; for some more details.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;pandoc&lt;/code&gt; invocation is now through a script:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; chmod +x md2pdf.sh
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; ./md2pdf.sh sample_1.md sample_1_settings.pdf
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do compare the pdf generated side by side with previous output before proceeding.&lt;/p&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; On my system, &lt;code&gt;DejaVu Serif&lt;/code&gt; did not have &lt;em&gt;italic&lt;/em&gt; variation installed, so I had to use &lt;code&gt;sudo apt install ttf-dejavu-extra&lt;/code&gt; to get it.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="syntax-highlighting"&gt;Syntax highlighting&lt;a class="zola-anchor" href="#syntax-highlighting"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One option to customize syntax highlighting for code snippets is to save one of the &lt;code&gt;pandoc&lt;/code&gt; themes and editing it. See &lt;a href="https://stackoverflow.com/a/47876166/4082052"&gt;stackoverflow: What are the available syntax highlighters?&lt;/a&gt; for available themes and more details (as a good practice on stackoverflow, go through all answers and comments — the linked/related sections on sidebar are useful as well).&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pandoc&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; --print-highlight-style&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;pygments &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; pygments.theme
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit the above file to customize the theme. Use sites like &lt;a href="https://www.colorhexa.com/"&gt;colorhexa&lt;/a&gt; to help with color choices, hex values, etc. For this demo, the below settings are changed:&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;# by default, background is same as normal text
&lt;/span&gt;&lt;span&gt;# change it to a shade of gray to easily distinguish code and text
&lt;/span&gt;&lt;span&gt;&amp;quot;background-color&amp;quot;: &amp;quot;#f8f8f8&amp;quot;,
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# change italic to false, messes up comments with slashes
&lt;/span&gt;&lt;span&gt;# change comment text-color to yet another shade of gray
&lt;/span&gt;&lt;span&gt;&amp;quot;Comment&amp;quot;: {
&lt;/span&gt;&lt;span&gt;    &amp;quot;text-color&amp;quot;: &amp;quot;#9c9c9c&amp;quot;,
&lt;/span&gt;&lt;span&gt;    &amp;quot;background-color&amp;quot;: null,
&lt;/span&gt;&lt;span&gt;    &amp;quot;bold&amp;quot;: false,
&lt;/span&gt;&lt;span&gt;    &amp;quot;italic&amp;quot;: false,
&lt;/span&gt;&lt;span&gt;    &amp;quot;underline&amp;quot;: false
&lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Inline code&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Similar to changing background color for code snippets, I found a &lt;a href="https://stackoverflow.com/q/40975004/4082052"&gt;solution online&lt;/a&gt; for &lt;em&gt;inline&lt;/em&gt; code snippets as well.&lt;/p&gt;
&lt;pre class="language-latex " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-latex"&gt;&lt;span style="color: #72ab00;"&gt;\usepackage&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;fancyvrb&lt;/span&gt;&lt;span&gt;,newverbs,xcolor}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\definecolor&lt;/span&gt;&lt;span&gt;{Light}{HTML}{F4F4F4}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\let\oldtexttt\texttt
&lt;/span&gt;&lt;span style="color: #668f14;"&gt;\renewcommand&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;\texttt&lt;/span&gt;&lt;span&gt;}[1]{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\colorbox&lt;/span&gt;&lt;span&gt;{Light}{&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\oldtexttt&lt;/span&gt;&lt;span&gt;{#1}}
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;--highlight-style pygments.theme&lt;/code&gt; and &lt;code&gt;--include-in-header inline_code.tex&lt;/code&gt; to the script and generate the &lt;code&gt;pdf&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;pandoc sample_2.md -f gfm -o sample_2.pdf&lt;/code&gt; the output would be:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Default syntax highlighting" src="/images/pandoc_pdf/default_syn.png" /&gt;&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;./md2pdf_syn.sh sample_2.md sample_2_syn.pdf&lt;/code&gt; the output is:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Customized syntax highlighting" src="/images/pandoc_pdf/customized_syn.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;For my &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; book, by chance I found that using &lt;code&gt;ruby&lt;/code&gt; instead of &lt;code&gt;python&lt;/code&gt; for REPL code snippets syntax highlighting was better. Snapshot from &lt;code&gt;./md2pdf_syn.sh sample_3.md sample_3.pdf&lt;/code&gt; result is shown below. For &lt;code&gt;python&lt;/code&gt; directive, string output gets treated as a comment and color for boolean values isn't easy to distinguish from string values. The &lt;code&gt;ruby&lt;/code&gt; directive treats string value as expected and boolean values are easier to spot.&lt;/p&gt;
&lt;p&gt;&lt;img alt="REPL syntax highlighting" src="/images/pandoc_pdf/python_vs_ruby_syn.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="bullet-styling"&gt;Bullet styling&lt;a class="zola-anchor" href="#bullet-styling"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This &lt;a href="https://stackoverflow.com/q/22156999/4082052"&gt;stackoverflow Q&amp;amp;A&lt;/a&gt; helped for bullet styling.&lt;/p&gt;
&lt;pre class="language-latex " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-latex"&gt;&lt;span style="color: #72ab00;"&gt;\usepackage&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;enumitem&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\usepackage&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;amsfonts&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;% level one
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\setlist&lt;/span&gt;&lt;span&gt;[itemize,1]{label=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\bullet&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;% level two
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\setlist&lt;/span&gt;&lt;span&gt;[itemize,2]{label=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\circ&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;% level three
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\setlist&lt;/span&gt;&lt;span&gt;[itemize,3]{label=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\star&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Comparing &lt;code&gt;pandoc sample_4.md -f gfm -o sample_4.pdf&lt;/code&gt; vs &lt;code&gt;./md2pdf_syn_bullet.sh sample_4.md sample_4_bullet.pdf&lt;/code&gt; gives:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bullet styling" src="/images/pandoc_pdf/bullet_styling.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="pdf-properties"&gt;PDF properties&lt;a class="zola-anchor" href="#pdf-properties"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This &lt;a href="https://tex.stackexchange.com/questions/23235/eliminate-edit-pdf-properties-added-by-pdflatex"&gt;tex.stackexchange Q&amp;amp;A&lt;/a&gt; helped to change metadata. See also &lt;a href="https://pspdfkit.com/blog/2018/whats-hiding-in-your-pdf/"&gt;pspdfkit: What’s Hiding in Your PDF?&lt;/a&gt; and &lt;a href="https://news.ycombinator.com/item?id=18381515"&gt;discussion on HN&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="language-latex " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-latex"&gt;&lt;span style="color: #72ab00;"&gt;\usepackage&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;hyperref&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\hypersetup&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  pdftitle={My awesome book},
&lt;/span&gt;&lt;span&gt;  pdfauthor={learnbyexample},
&lt;/span&gt;&lt;span&gt;  pdfsubject={pandoc},
&lt;/span&gt;&lt;span&gt;  pdfkeywords={pandoc,pdf,xelatex}
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;./md2pdf_syn_bullet_prop.sh sample_4.md sample_4_bullet_prop.pdf&lt;/code&gt; gives:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="pdf properties" src="/images/pandoc_pdf/pdf_properties.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="adding-table-of-contents"&gt;Adding table of contents&lt;a class="zola-anchor" href="#adding-table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There's a handy option &lt;code&gt;--toc&lt;/code&gt; to automatically include table of contents at top of the generated &lt;code&gt;pdf&lt;/code&gt;. You can control number of levels using &lt;code&gt;--toc-depth&lt;/code&gt; option, the default is 3 levels. You can also change the default string &lt;code&gt;Contents&lt;/code&gt; to something else using the &lt;code&gt;-V toc-title&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./md2pdf_syn_bullet_prop_toc.sh sample_1.md sample_1_toc.pdf&lt;/code&gt; gives:&lt;/p&gt;
&lt;p&gt;&lt;img alt="table of contents" src="/images/pandoc_pdf/table_of_contents.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="adding-cover-image"&gt;Adding cover image&lt;a class="zola-anchor" href="#adding-cover-image"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To add something prior to table of contents, cover image for example, you can use a &lt;code&gt;tex&lt;/code&gt; file and include it verbatim. Create a &lt;code&gt;tex&lt;/code&gt; file (named as &lt;code&gt;cover.tex&lt;/code&gt; here) with content as shown below:&lt;/p&gt;
&lt;pre class="language-tex " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-tex"&gt;&lt;span style="color: #b39f04;"&gt;\includegraphics&lt;/span&gt;&lt;span&gt;{cover.png}
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\thispagestyle&lt;/span&gt;&lt;span&gt;{empty}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, modify the previous script &lt;code&gt;md2pdf_syn_bullet_prop_toc.sh&lt;/code&gt; by adding &lt;code&gt;--include-before-body cover.tex&lt;/code&gt; and tada — you get the cover image before table of contents. &lt;code&gt;\thispagestyle{empty}&lt;/code&gt; helps to avoid page number on the cover page, see also &lt;a href="https://tex.stackexchange.com/questions/360739/what-is-the-use-of-clearpage-thispagestyleempty-cleardoublepage"&gt;tex.stackexchange: clear page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;bash&lt;/code&gt; script invocation is now &lt;code&gt;./md2pdf_syn_bullet_prop_toc_cover.sh sample_5.md sample_5.pdf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; You'll need at least one image in input markdown file, otherwise settings won't apply to the cover image and you may end up with a weird output. &lt;code&gt;sample_5.md&lt;/code&gt; used in the command above includes an image. And be careful to use escapes if the image path can contain &lt;code&gt;tex&lt;/code&gt; metacharacters.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="stylish-blockquote"&gt;Stylish blockquote&lt;a class="zola-anchor" href="#stylish-blockquote"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, blockquotes (lines starting with &lt;code&gt;&amp;gt;&lt;/code&gt; in markdown) are just indented in the &lt;code&gt;pdf&lt;/code&gt; output. To make them standout, &lt;a href="https://tex.stackexchange.com/questions/154528/how-to-change-the-background-color-and-border-of-a-pandoc-generated-blockquote"&gt;tex.stackexchange: change the background color and border of blockquote&lt;/a&gt; helped.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;quote.tex&lt;/code&gt; with the contents as shown below. You can change the colors to suit your own preferred style.&lt;/p&gt;
&lt;pre class="language-tex " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-tex"&gt;&lt;span style="color: #72ab00;"&gt;\usepackage&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;tcolorbox&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\newtcolorbox&lt;/span&gt;&lt;span&gt;{myquote}{colback=red!5!white, colframe=red!75!black}
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;\renewenvironment&lt;/span&gt;&lt;span&gt;{quote}{&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\begin&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;myquote&lt;/span&gt;&lt;span&gt;}}{&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\end&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;myquote&lt;/span&gt;&lt;span&gt;}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;bash&lt;/code&gt; script invocation is now &lt;code&gt;./md2pdf_syn_bullet_prop_toc_cover_quote.sh sample_5.md sample_5_quote.pdf&lt;/code&gt;. The difference between default and styled blockquote is shown below.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="styling blockquotes" src="/images/pandoc_pdf/styled_blockquote.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="customizing-epub"&gt;Customizing epub&lt;a class="zola-anchor" href="#customizing-epub"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a long time, I thought &lt;code&gt;epub&lt;/code&gt; didn't make sense for programming books. Turned out, I wasn't using the right ebook readers. &lt;strong&gt;FBReader&lt;/strong&gt; was good for novels but not ebooks with code snippets. When I used &lt;a href="https://github.com/mate-desktop/atril"&gt;atril&lt;/a&gt;, &lt;a href="https://github.com/johnfactotum/foliate"&gt;foliate&lt;/a&gt; or &lt;a href="https://calibre-ebook.com/"&gt;calibre ebook-viewer&lt;/a&gt;, the results were good.&lt;/p&gt;
&lt;p&gt;I didn't know how to use &lt;code&gt;css&lt;/code&gt; before trying to generate the &lt;code&gt;epub&lt;/code&gt; version. Somehow, I managed to take the default &lt;a href="https://github.com/jgm/pandoc/blob/master/data/epub.css"&gt;epub.css&lt;/a&gt; provided by &lt;code&gt;pandoc&lt;/code&gt; and customize it as close as possible to the &lt;code&gt;pdf&lt;/code&gt; version. The modified &lt;code&gt;epub.css&lt;/code&gt; is available from the &lt;a href="https://github.com/learnbyexample/learnbyexample.github.io/tree/master/files/pandoc_pdf"&gt;learnbyexample.github.io repo&lt;/a&gt;. The &lt;code&gt;bash&lt;/code&gt; script to generate the &lt;code&gt;epub&lt;/code&gt; is shown below and invoked as &lt;code&gt;./md2epub.sh sample_5.md sample_5.epub&lt;/code&gt;. Note that &lt;code&gt;pygments.theme&lt;/code&gt; is same as the &lt;code&gt;pdf&lt;/code&gt; customization discussed before.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;#!/bin/bash
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;pandoc  &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;1&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        -f&lt;/span&gt;&lt;span&gt; gfm \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --toc &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --standalone &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --top-level-division&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;chapter \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --highlight-style&lt;/span&gt;&lt;span&gt; pygments.theme \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --css&lt;/span&gt;&lt;span&gt; epub.css \
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --metadata&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;title:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;My awesome book&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --metadata&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;author:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;learnbyexample&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --metadata&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;lang:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;en-US&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        --metadata&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;cover-image:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;cover.png&amp;quot; &lt;/span&gt;&lt;span&gt;\
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;        -o &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;2&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="resource-links"&gt;Resource links&lt;a class="zola-anchor" href="#resource-links"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pandoc.org/MANUAL.html"&gt;pandoc: manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pandoc.org/demos.html"&gt;pandoc: demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jgm/pandoc/wiki/Pandoc-Tricks"&gt;pandoc: tips and tricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;More options and workflows for generating ebooks&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Wandmalfarbe/pandoc-latex-template"&gt;pandoc-latex-template&lt;/a&gt; — a clean pandoc LaTeX template to convert your markdown files to PDF or LaTeX&lt;/li&gt;
&lt;li&gt;&lt;a href="https://keleshev.com/writing-a-book-with-pandoc-make-and-vim/"&gt;Writing a book with pandoc, make, and vim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://quarto.org/"&gt;Quarto&lt;/a&gt; — open source scientific and technical publishing system built on Pandoc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iamgio/quarkdown"&gt;quarkdown&lt;/a&gt; — a modern Markdown-based typetting system&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/typst/typst"&gt;typst&lt;/a&gt; — a new markup-based typesetting system that is powerful and easy to learn&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jupyterbook.org/en/stable/intro.html"&gt;Jupyter Book&lt;/a&gt; — open source project for building beautiful, publication-quality books and documents from computational material
&lt;ul&gt;
&lt;li&gt;See also &lt;a href="https://github.com/fastai/fastdoc"&gt;fastdoc&lt;/a&gt; — the output of fastdoc is an asciidoc file for each input notebook. You can then use asciidoctor to convert that to HTML, DocBook, epub, mobi, and so forth&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thedigitalcatbooks.com/maubook-introduction/"&gt;Mau&lt;/a&gt; — template-based markup language, heavily inspired by AsciiDoc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://asciidoctor.org/docs/what-is-asciidoc/"&gt;Asciidoctor&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/daneah/asciidoc-book-template"&gt;Asciidoc book template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shape-of-code.coding-guidelines.com/2019/08/11/my-books-pdf-generation-workflow/"&gt;pdf generation workflow with Asciidoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sphinx-doc.org/en/master/index.html"&gt;Sphinx&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://digitalsuperpowers.com/blog/2019-02-16-publishing-ebook.html"&gt;Self-publishing a book with reStructuredText, Sphinx, Calibre, and vim&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bookdown.org/home/"&gt;Bookdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orgmode.org/"&gt;Emacs orgmode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://casual-effects.com/markdeep/"&gt;Markdeep&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Miscellaneous&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nickjanetakis.com/blog/vim-is-saving-me-hours-of-work-when-writing-books-and-courses"&gt;Vim is saving me hours of work when writing books &amp;amp; courses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://joecmarshall.com/posts/book-writing-environment/"&gt;Writing a Book with Unix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://askubuntu.com/questions/3697/how-do-i-install-fonts"&gt;askubuntu: How do I install fonts?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tex.stackexchange.com/questions/9533/what-best-combination-of-fonts-for-serif-sans-and-mono-do-you-recommend"&gt;tex.stackexchange: What best combination of fonts for Serif, Sans, and Mono do you recommend?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tug.org/FontCatalogue/"&gt;LaTeX font catalogue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/karthik/markdown_science/wiki/Tools-to-support-your-markdown-authoring"&gt;Tools to support markdown authoring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://picular.co/"&gt;picular: search engine for colors&lt;/a&gt; and &lt;a href="https://www.colorhexa.com/"&gt;colorhexa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ebooks.stackexchange.com/questions?sort=votes"&gt;ebooks.stackexchange&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Wed, 04 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/customizing-pandoc/</guid></item><item><title>moka pot notes</title><link>https://dotat.at/@/2025-06-01-bialetti.html</link><description>&lt;p&gt;In hot weather I like to drink my coffee in an iced latte. To make it,
I have a very large &lt;a href="https://www.bialetti.com/it_en/moka-express.html"&gt;Bialetti Moka Express&lt;/a&gt;. Recently when I
got it going again after a winter of disuse, it took me a couple of
attempts to get the technique right, so here are some notes as a
reminder to my future self next year.&lt;/p&gt;
&lt;p&gt;It’s worth noting that I’m not fussy about my coffee: I usually drink
pre-ground beans from the supermarket, with cream (in winter hot
coffee) or milk and ice.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-01-bialetti.html#basic-principle" name="basic-principle"&gt;basic principle&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I was getting the hang of my moka pot, I learned from YouTube
coffee geeks such as &lt;a href="https://www.youtube.com/watch?v=zK0F5PqJ1Gk"&gt;James Hoffmann&lt;/a&gt; that the main aim is
for the water to be pushed through the coffee smoothly and gently.
Better to err on the side of too little flow than too much.&lt;/p&gt;
&lt;p&gt;I have not had much success trying to make fine temperature
adjustments while the coffee is brewing, because the big moka pot has
a lot of thermal inertia: it takes a long time for any change in gas
level to have any effect on on the coffee flow.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-01-bialetti.html#routine" name="routine"&gt;routine&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;fill the kettle and turn it on&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;put the moka pot’s basket in a mug to keep it stable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;fill it with coffee (mine needs about 4 &lt;a href="https://www.aeropress.co.uk/collections/aeropress-spare-parts/products/aeropress-coffee-scoop"&gt;Aeropress scoops&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tamp it down firmly [1]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;when the kettle has boiled, fill the base of the pot to just below
the pressure valve (which is also just below the filter screen in
the basket)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;insert the coffee basket, making sure there are no stray grounds
around the edge where the seal will mate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;screw on the upper chamber firmly&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;put it on a small gas ring turned up to the max [2]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;leave the lid open and wait for the coffee to emerge&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;immediately turn the gas down to the minimum [3]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the coffee should now come out in a steady thin stream without
spluttering or stalling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;when the upper chamber is filled near the mouths of the central
spout, it’ll start fizzing or spluttering [4]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;turn off the gas and pour the coffee into a carafe&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-06-01-bialetti.html#notes" name="notes"&gt;notes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If I don’t tamp the grounds, the pot tends to splutter. I guess
tamping gives the puck better integrity to resist channelling, and
to keep the water under even pressure. Might be an effect of the
relatively coarse supermarket grind?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It takes a &lt;em&gt;long&lt;/em&gt; time to get the pot back up to boiling point and
I’m not sure that heating it up slower helps. The main risk, I
think, is overshooting the ideal steady brewing state too much, but:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With my moka pot on my hob the lowest gas flow on the smallest
rings is just enough to keep the coffee flowing without stalling.
The flow when the coffee first emerges is relatively fast, and it
slows to the steady state several seconds after I turn the heat
down, so I think the overshoot isn’t too bad.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This routine turns almost all of the water into coffee, which
Hoffmann suggests is a good result, and a sign that the pressure
and temperature aren’t getting too high.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>Tony Finch's blog</author><pubDate>Sun, 01 Jun 2025 18:56:02 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-06-01-bialetti.html</guid></item><item><title>Old Desktop to Linux - Wayland is still not ready</title><link>https://boyter.org/posts/upgrading-my-old-desktop-linux/</link><description>&lt;p&gt;Back in August 2021 during the covid madness I updated my rather aged desktop to have new CPU, GPU, PSU and fans. The goal being to make it quieter and get more life out of the system. I also kept it running Windows at the time, since I already have a stable Windows 10 install on it. However it is now 2025 and we are getting close to the end of life time for it in October. I never had any major issues with Windows up till 10. However Microsoft&amp;rsquo;s increasingly hostile approach to the hardware I paid for can no longer be overlooked. Similiar to how I cancelled netflix recently, I am not paying in order to watch ad&amp;rsquo;s. Coupled with the invasion of privacy of not allowing local accounts it was time to swich back to some flavour of Linux.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 01 Jun 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/upgrading-my-old-desktop-linux/</guid></item><item><title>the algebra of dependent types</title><link>https://dotat.at/@/2025-05-28-types.html</link><description>&lt;p&gt;TIL (or this week-ish I learned) why big-sigma and big-pi turn up in
the notation of dependent type theory.&lt;/p&gt;
&lt;p&gt;I’ve long been aware of the zoo of more obscure Greek letters that
turn up in papers about type system features of functional programming
languages, &lt;code&gt;μ&lt;/code&gt;, &lt;code&gt;Λ&lt;/code&gt;, &lt;code&gt;Π&lt;/code&gt;, &lt;code&gt;Σ&lt;/code&gt;. Their meaning is usually clear from context
but the reason for the choice of notation is usually not explained.&lt;/p&gt;
&lt;p&gt;I recently stumbled on an explanation for &lt;code&gt;Π&lt;/code&gt; (dependent functions)
and &lt;code&gt;Σ&lt;/code&gt; (dependent pairs) which turn out to be nicer than I expected,
and closely related to every-day algebraic data types.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-28-types.html#sizes-of-types" name="sizes-of-types"&gt;sizes of types&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The easiest way to understand algebraic data types is by counting the
inhabitants of a type. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the unit type &lt;code&gt;()&lt;/code&gt; has one inhabitant, &lt;code&gt;()&lt;/code&gt;, and the number 1 is
why it’s called the unit type;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;bool&lt;/code&gt; type hass two inhabitants, &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have even seen these types called &lt;strong&gt;&lt;code&gt;1&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;2&lt;/code&gt;&lt;/strong&gt; (cruelly,
without explanation) in occasional papers.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-28-types.html#product-types" name="product-types"&gt;product types&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Or pairs or (more generally) tuples or records. Usually written,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    (A, B)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pair contains an &lt;em&gt;A&lt;/em&gt; and a &lt;em&gt;B&lt;/em&gt;, so the number of possible values is
the number of possible &lt;em&gt;A&lt;/em&gt; values multiplied by the number of possible
&lt;em&gt;B&lt;/em&gt; values.&lt;/p&gt;
&lt;p&gt;So it is spelled in type theory (and in Standard ML) like,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    A * B
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-28-types.html#sum-types" name="sum-types"&gt;sum types&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Or disjoint union, or variant record. Declared in Haskell like,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-hs"&gt;        &lt;span class="hilite keyword"&gt;data&lt;/span&gt; &lt;span class="hilite type"&gt;Either&lt;/span&gt; &lt;span class="hilite type"&gt;a&lt;/span&gt; &lt;span class="hilite type"&gt;b&lt;/span&gt;
            &lt;span class="hilite operator"&gt;=&lt;/span&gt; &lt;span class="hilite constructor"&gt;Left&lt;/span&gt; &lt;span class="hilite type"&gt;a&lt;/span&gt;
            &lt;span class="hilite operator"&gt;|&lt;/span&gt; &lt;span class="hilite constructor"&gt;Right&lt;/span&gt; &lt;span class="hilite type"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or in Rust like,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;        &lt;span class="hilite keyword"&gt;enum&lt;/span&gt; &lt;span class="hilite type"&gt;Either&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;&amp;lt;&lt;/span&gt;&lt;span class="hilite type"&gt;A&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt; &lt;span class="hilite type"&gt;B&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;&amp;gt;&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt;
            &lt;span class="hilite constructor"&gt;Left&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite type"&gt;A&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;
            &lt;span class="hilite constructor"&gt;Right&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite type"&gt;B&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;
        &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A value of the type is either an &lt;em&gt;A&lt;/em&gt; or a &lt;em&gt;B&lt;/em&gt;, so the number of
possible values is the number of &lt;em&gt;A&lt;/em&gt; values plus the number of &lt;em&gt;B&lt;/em&gt;
values.&lt;/p&gt;
&lt;p&gt;So it is spelled in type theory like,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    A + B
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-28-types.html#dependent-pairs" name="dependent-pairs"&gt;dependent pairs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In a dependent pair, the &lt;em&gt;type&lt;/em&gt; of the second element depends on the
&lt;em&gt;value&lt;/em&gt; of the first. The classic example is a slice, roughly,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;        &lt;span class="hilite keyword"&gt;struct&lt;/span&gt; &lt;span class="hilite type"&gt;IntSlice&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt;
            &lt;span class="hilite property"&gt;len&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite type builtin"&gt;usize&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;
            &lt;span class="hilite property"&gt;elem&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite operator"&gt;&amp;amp;&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;[&lt;/span&gt;&lt;span class="hilite type builtin"&gt;i64&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;;&lt;/span&gt; len&lt;span class="hilite punctuation bracket"&gt;]&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;,&lt;/span&gt;
        &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(This might look a bit circular, but the idea is that an array &lt;code&gt;[i64; N]&lt;/code&gt; must be told how big it is – its size is an explicit part of its
type – but an &lt;code&gt;IntSlice&lt;/code&gt; knows its own size. The traditional
dependent “vector” type is a sized linked list, more like my array
type than my slice type.)&lt;/p&gt;
&lt;p&gt;The classic way to write a dependent pair in type theory is like,&lt;/p&gt;
&lt;p&gt;     &lt;big&gt;Σ&lt;/big&gt; len: usize . Array(Int, len)&lt;/p&gt;
&lt;p&gt;The big sigma binds a variable that has a type annotation, with a
scope covering the expression after the dot – similar syntax to a
typed lambda expression.&lt;/p&gt;
&lt;p&gt;We can expand a simple example like this into a many-armed sum type:
either an array of length zero, or an array of length 1, or an array
of length 2, … but in a sigma type the discriminant is user-defined
instead of hidden.&lt;/p&gt;
&lt;p&gt;The number of possible values of the type comes from adding up all the
alternatives, a summation just like the big sigma summation we were
taught in school.&lt;/p&gt;
&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;
  &lt;munder&gt;
    &lt;mo&gt;&amp;amp;Sum&lt;/mo&gt;
    &lt;mrow&gt;
      &lt;mi&gt;a&lt;/mi&gt;
      &lt;mo&gt;&amp;amp;in&lt;/mo&gt;
      &lt;mi&gt;A&lt;/mi&gt;
    &lt;/mrow&gt;
  &lt;/munder&gt;
  &lt;mspace width="0.5em"&gt;
  &lt;msub&gt;
    &lt;mi&gt;B&lt;/mi&gt;
    &lt;mi&gt;a&lt;/mi&gt;
  &lt;/msub&gt;
&lt;/math&gt;
&lt;p&gt;When the second element doesn’t depend on the first element, we can
count the inhabitants like,&lt;/p&gt;
&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;
  &lt;munder&gt;
    &lt;mo&gt;&amp;amp;Sum&lt;/mo&gt;
    &lt;mrow&gt;
      &lt;mi&gt;A&lt;/mi&gt;
    &lt;/mrow&gt;
  &lt;/munder&gt;
  &lt;mspace width="0.5em"&gt;
  &lt;mi&gt;B&lt;/mi&gt;
  &lt;mspace width="1em"&gt;
  &lt;mo&gt;=&lt;/mo&gt;
  &lt;mspace width="1em"&gt;
  &lt;mi&gt;A&lt;/mi&gt;&lt;mo&gt;*&lt;/mo&gt;&lt;mi&gt;B&lt;/mi&gt;
&lt;/math&gt;
&lt;p&gt;And the sigma type simplifies to a product type.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-05-28-types.html#telescopes" name="telescopes"&gt;telescopes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An aside from the main topic of these notes, I also recently
encountered the name “telescope” for a multi-part dependent tuple or
record.&lt;/p&gt;
&lt;p&gt;The name “telescope” comes from de Bruijn’s AUTOMATH, one of the first
computerized proof assistants. (I first encountered de Bruijn as the
inventor of numbered lambda bindings.)&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-28-types.html#dependent-functions" name="dependent-functions"&gt;dependent functions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The return type of a dependent function can vary according to the
argument it is passed.&lt;/p&gt;
&lt;p&gt;For example, to construct an array we might write something like,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;        &lt;span class="hilite keyword"&gt;fn&lt;/span&gt; &lt;span class="hilite function"&gt;repeat_zero&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;(&lt;/span&gt;&lt;span class="hilite variable parameter"&gt;len&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;:&lt;/span&gt; &lt;span class="hilite type builtin"&gt;usize&lt;/span&gt;&lt;span class="hilite punctuation bracket"&gt;)&lt;/span&gt; -&amp;gt; &lt;span class="hilite punctuation bracket"&gt;[&lt;/span&gt;&lt;span class="hilite type builtin"&gt;i64&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;;&lt;/span&gt; len&lt;span class="hilite punctuation bracket"&gt;]&lt;/span&gt; &lt;span class="hilite punctuation bracket"&gt;{&lt;/span&gt;
            &lt;span class="hilite punctuation bracket"&gt;[&lt;/span&gt;&lt;span class="hilite constant builtin"&gt;0&lt;/span&gt;&lt;span class="hilite punctuation delimiter"&gt;;&lt;/span&gt; len&lt;span class="hilite punctuation bracket"&gt;]&lt;/span&gt;
        &lt;span class="hilite punctuation bracket"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The classic way to write the type of &lt;code&gt;repeat_zero()&lt;/code&gt; is very similar
to the &lt;code&gt;IntSlice&lt;/code&gt; dependent pair, but with a big pi instead of a big
sigma:&lt;/p&gt;
&lt;p&gt;     &lt;big&gt;Π&lt;/big&gt; len: usize . Array(Int, len)&lt;/p&gt;
&lt;p&gt;Mmm, pie.&lt;/p&gt;
&lt;p&gt;To count the number of possible (pure, total) functions &lt;em&gt;A&lt;/em&gt; ➞ &lt;em&gt;B&lt;/em&gt;, we
can think of each function as a big lookup table with &lt;em&gt;A&lt;/em&gt; entries each
containing a &lt;em&gt;B&lt;/em&gt;. That is, a big tuple (&lt;em&gt;B&lt;/em&gt;, &lt;em&gt;B&lt;/em&gt;, … &lt;em&gt;B&lt;/em&gt;), that is,
&lt;em&gt;B&lt;/em&gt; * &lt;em&gt;B&lt;/em&gt; * … * &lt;em&gt;B&lt;/em&gt;, that is, &lt;em&gt;B&lt;/em&gt;&lt;sup&gt;&lt;em&gt;A&lt;/em&gt;&lt;/sup&gt;. Functions are
exponential types.&lt;/p&gt;
&lt;p&gt;We can count a dependent function, where the number of possible
&lt;i&gt;B&lt;/i&gt;s depends on which &lt;em&gt;A&lt;/em&gt; we are passed,&lt;/p&gt;
&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;
  &lt;munder&gt;
    &lt;mo&gt;&amp;amp;Product&lt;/mo&gt;
    &lt;mrow&gt;
      &lt;mi&gt;a&lt;/mi&gt;
      &lt;mo&gt;&amp;amp;in&lt;/mo&gt;
      &lt;mi&gt;A&lt;/mi&gt;
    &lt;/mrow&gt;
  &lt;/munder&gt;
  &lt;mspace width="0.5em"&gt;
  &lt;msub&gt;
    &lt;mi&gt;B&lt;/mi&gt;
    &lt;mi&gt;a&lt;/mi&gt;
  &lt;/msub&gt;
&lt;/math&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-28-types.html#danger" name="danger"&gt;danger&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have avoided the terms “dependent sum” and “dependent product”,
because they seem perfectly designed to cause confusion over whether I
am talking about variants, records, or functions.&lt;/p&gt;
&lt;p&gt;It kind of makes me want to avoid algebraic data type jargon, except
that there isn’t a good alternative for “sum type”. Hmf.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Thu, 29 May 2025 03:26:38 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-05-28-types.html</guid></item><item><title>Best Way to Run Unsigned Binaries in Terminal on macOS</title><link>https://donatstudios.com/mac-terminal-run-unsigned-binaries</link><description>&lt;p&gt;With the introduction of Apple Silicon, macOS became much stricter about running unsigned binaries. It even refuses to execute them in the Terminal. This can be frustrating as many precompiled tools, once easily downloadable from sources like GitHub, may no longer work as expected.&lt;/p&gt;
&lt;p&gt;By default, if you try to run an unsigned binary in Terminal, you will see an error like:&lt;/p&gt;

  &lt;source type="image/avif" /&gt;
  &lt;img alt="Message: Apple could not verify [binary] is free of malware that may harm your Mac or compromise your privacy" src="https://donatstudios.com/assets/86/warning.png" title="Apple could not verify [binary] is free of malware that may harm your Mac or compromise your privacy" /&gt;

&lt;blockquote&gt;
&lt;p&gt;Apple could not verify &amp;quot;[binary]&amp;quot; is free of malware that may harm your Mac or compromise your privacy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead of offering any help, macOS simply offers to move the binary to the trash. Very frustrating.&lt;/p&gt;
&lt;p&gt;You find a lot of complicated solutions online, such as manually unquarantining and self-signing the individual binaries with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ xattr -dr com.apple.quarantine ./[binary]
$ codesign -s - --deep --force ./[binary]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fortunately, there's a simple solution: add Terminal as a system &lt;strong&gt;Developer Tool&lt;/strong&gt; in &lt;em&gt;System Settings&lt;/em&gt;. &lt;/p&gt;
&lt;p&gt;This will just allow it to run unsigned binaries. No fuss, no muss.&lt;/p&gt;
&lt;p&gt;To do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;System Settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Search for &amp;quot;developer&amp;quot;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Allow applications to use developer tools&lt;/strong&gt; in the sidebar.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If Terminal is not already listed, click the &lt;code&gt;+&lt;/code&gt; and search for it:&lt;/p&gt;

  &lt;source type="image/avif" /&gt;
  &lt;img alt="Shows 'System Settings' app with 'Allow applications to use developer tools' selected and an arrow pointing to the plus option" src="https://donatstudios.com/assets/86/add-terminal.png" title="System Settings: Allow applications to use developer tools - Add App" /&gt;

&lt;p&gt;Search for &lt;code&gt;Terminal&lt;/code&gt; in the file dialog that appears and select it.&lt;/p&gt;

  &lt;source type="image/avif" /&gt;
  &lt;img alt="File Chooser searching spotlight for Terminal" src="https://donatstudios.com/assets/86/search.png" title="Search for 'Terminal'" /&gt;

&lt;p&gt;Once added, ensure the toggle next to Terminal is &lt;strong&gt;enabled&lt;/strong&gt;.&lt;/p&gt;

  &lt;source type="image/avif" /&gt;
  &lt;img alt="Arrow points to the slider knob to enable Terminal in System Settings" src="https://donatstudios.com/assets/86/enable-terminal.png" title="Make sure the slider is in the enabled position" /&gt;

&lt;p&gt;Finally, &lt;strong&gt;restart Terminal&lt;/strong&gt; and everything should now work:&lt;/p&gt;

  &lt;source type="image/avif" /&gt;
  &lt;img alt="Terminal window showing the previous app that died in error is now a success" src="https://donatstudios.com/assets/86/success.png" title="Success!" /&gt;

&lt;p&gt;There you have it. You may now run any unsigned binary from Terminal without issues.&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Thu, 29 May 2025 03:00:17 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/mac-terminal-run-unsigned-binaries</guid></item><item><title>Profiling Websites</title><link>https://www.marginalia.nu/log/a_121_profiling_websites/</link><description>&lt;p&gt;The most recent change to the search engine is a system that profiles websites based on their rendered DOM. The goal is identifying advertisements, trackers, nuisance popovers, and similar elements.&lt;/p&gt;
&lt;p&gt;The search engine already tries to do this, but isn&amp;rsquo;t very good at it because it&amp;rsquo;s only looking at static code.&lt;/p&gt;
&lt;p&gt;It turns out to be somewhat difficult to determine what a website that has non-trivial javascript will look like based its source code alone, as this would require us to among other things solve the halting problem.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 29 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_121_profiling_websites/</guid></item><item><title>Better bindings for command line history search</title><link>https://learnbyexample.github.io/mini/better-bindings-cli-history-search/</link><description>&lt;p&gt;Do you find it confusing to use &lt;code&gt;Ctrl+r&lt;/code&gt; for searching a command from shell history? I have the following mappings in the &lt;code&gt;~/.inputrc&lt;/code&gt; file, so that I can use the &lt;code&gt;↑&lt;/code&gt; and &lt;code&gt;↓&lt;/code&gt; arrow keys instead. Note that this assumes you are using Emacs-style key bindings instead of Vi mode.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;\e[A&amp;quot;&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;:&lt;/span&gt;&lt;span&gt; history-search-backward
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;\e[B&amp;quot;&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;:&lt;/span&gt;&lt;span&gt; history-search-forward
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normally, when you use the &lt;code&gt;↑&lt;/code&gt; arrow key, you'll get the previous command from the history. You can repeatedly press the key to get more entries. With the above settings active, this behavior will change if you have some characters already typed before pressing the arrow key — you'll only get entries from the history that match these characters from the start of the command. If there are multiple matches, you can use the &lt;code&gt;↑&lt;/code&gt; and &lt;code&gt;↓&lt;/code&gt; keys repeatedly to move backwards and forwards through the list.&lt;/p&gt;
&lt;p&gt;If you want the matching to happen anywhere in command (same behavior as &lt;code&gt;Ctrl+r&lt;/code&gt; and &lt;code&gt;Ctrl+s&lt;/code&gt;), use the following lines instead:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;\e[A&amp;quot;&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;:&lt;/span&gt;&lt;span&gt; history-substring-search-backward
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;\e[B&amp;quot;&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;:&lt;/span&gt;&lt;span&gt; history-substring-search-forward
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;~/.inputrc&lt;/code&gt; file affects any shell using the &lt;code&gt;readline&lt;/code&gt; library (for example, programming language REPLs). You can use the &lt;code&gt;bind&lt;/code&gt; command and put them in, &lt;code&gt;~/.bashrc&lt;/code&gt; for example, to affect only that particular shell.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #b39f04;"&gt;bind &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;\e[A&amp;quot;: history-search-backward'
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;bind &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;\e[B&amp;quot;: history-search-forward'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/title/Readline"&gt;Introduction to readline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cupfullofcode.com/blog/2013/07/03/efficient-command-line-navigation/index.html"&gt;Efficient command line navigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/junegunn/fzf"&gt;fzf: general purpose command line fuzzy finder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mikebian.co/supercharged-zsh-command-history/"&gt;Supercharged zsh command history&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Tue, 27 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/mini/better-bindings-cli-history-search/</guid></item><item><title>Cheerleading</title><link>http://notes.eatonphil.com/2025-05-26-cheerleading.html</link><description>&lt;p&gt;At work we're so absorbed in the difficulties we face that it becomes
easy to forget what we appreciate and what we value in our
coworkers. On social media we can be so focused on our own work that
we forget to interact with others'.&lt;/p&gt;
&lt;p&gt;I have heard so many times from founders that they switch between
heads down building, disappearing from the community, and then pop up
to talk about what they've built. Engineers often say the exact same
thing. This kills me. Though I probably still do it to a degree too.&lt;/p&gt;
&lt;p&gt;The easiest way for no one to give a shit about what you've done is
for you to only interact at periodic intervals that are only
convenient for you.&lt;/p&gt;
&lt;p&gt;With networking in general the most important time to interact and
build relationships is when you do not need anything. By the time you
want someone's support or you want someone to care about what you've
done it is way too late to start building a relationship.&lt;/p&gt;
&lt;p&gt;One of the cheapest and most effective avenues to build genuine
relationships is to be the biggest cheerleader you can be.&lt;/p&gt;
&lt;p&gt;At work this means when someone writes a fantastic blog post you call
it out in a public channel. It means when a coworker's work is
featured somewhere you call this out in a public channel. At least,
this is what I do at work.&lt;/p&gt;
&lt;p&gt;I don't care that I have no leadership role or haven't met these
people before. If I see something amazing that I'd like to see more
of, I mention it in public and praise the person.&lt;/p&gt;
&lt;p&gt;Or when someone shares work they've done in a public channel, you hit
all the emojis and you reply "that's awesome".&lt;/p&gt;
&lt;p&gt;On social media this means engaging seriously and (more) deeply with
what people post. If the default behavior on social media is to
passively observe, engaging more deeply can be liking a post. If you
already show support through liking, engaging more deeply can be
commenting or asking questions or (kindly) pointing out mistakes.&lt;/p&gt;
&lt;p&gt;Engagement is a spectrum. Genuine engagement consistently over time
builds genuine relationships.&lt;/p&gt;
&lt;p&gt;I would not suggest that you do this without feeling it, or I'd rather
only encourage you to respond to the degree that you feel. But I
personally do feel so strongly when I cheer people on.&lt;/p&gt;
&lt;p&gt;So much of life and work is drudgery such that when you see something
positive, someone taking initiative, someone with talent or potential
doing something with their skills, how can you not feel an
overwhelming urge to cheer them on and hope to see more of it? Hope to
see it develop?&lt;/p&gt;
&lt;p&gt;What's more, I want to be around people who are trying new things and
improving themselves. I want to be around people who celebrate. So I
in turn try new things and work to improve myself and I celebrate the
people around me.&lt;/p&gt;
&lt;p&gt;This energy is infectious. And I genuinely think even a single person
in a group celebrating publicly changes the group dynamic.&lt;/p&gt;
&lt;p&gt;And I don't expect people to reciprocate in the same way or to the
same degree that I show support. I'm particularly weird and confident
and vocal. But if my cheerleading for some person seems like a
complete sink then I'll not continue it and invest my time and energy
elsewhere.&lt;/p&gt;
&lt;p&gt;Not everybody needs my support and that's ok! It is better that it's
spent where it's most needed. There were people who used to cheer me
on who don't much anymore and that's totally fine, I still have
positive feelings for them for the time they did cheer me on.&lt;/p&gt;
&lt;p&gt;Cheerleading electrifies the work atmosphere. It is the proper use of
social media. Cheerleading is the imperative of caring individuals as
they become more experienced, gain confidence, command a wider
audience, and want to continue their own growth through the
development of genuine and supportive networks.&lt;/p&gt;
&lt;p&gt;And an ambitious, high-achieving, celebratory culture is simply the
most fun to be a part of.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a post on being a cheerleader. &lt;a href="https://t.co/mESNjSjQ0n"&gt;pic.twitter.com/mESNjSjQ0n&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1927031793204560023?ref_src=twsrc%5Etfw"&gt;May 26, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 26 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-05-26-cheerleading.html</guid></item><item><title>Elixir/Phoenix Liveview was a mistake</title><link>https://www.swyx.io/liveview-mistake</link><description>&lt;p&gt;I made an expensive technical decision on Phoenix Liveview for the Smol Talk webapp about a year ago that I now regret, and am jotting down some notes to self for why.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Fri, 23 May 2025 09:36:20 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/liveview-mistake</guid></item><item><title>A 2030 morning routine</title><link>https://www.marginalia.nu/log/a_120_morning_routine_2030/</link><description>&lt;p&gt;You wake at 05:30 in the morning, feeling somewhat groggy.&lt;/p&gt;
&lt;p&gt;Instead of the alarm clock ringing like it normally does, a cheerful hologram appears: &amp;ldquo;Hi! I&amp;rsquo;m Kyle, your new alarm clock assistant!&amp;rdquo; You get dressed as Kyle explains all of the fantastic things he is capable of.&lt;/p&gt;
&lt;p&gt;You head over to the coffee machine. &amp;ldquo;Hey there! I&amp;rsquo;m Evan! Are you ready for AI in your coffee? But first - tell me about yourself!&amp;rdquo;. You ignore Evan&amp;rsquo;s monologue and close your eyes as the synthetic coffee replacement is brewing. Real coffee costs more than your coffee maker nowadays, so it has to suffice.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 23 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_120_morning_routine_2030/</guid></item><item><title>Toronto Fish &amp;amp; Chips</title><link>https://kevincox.ca/2025/05/03/toronto-fish-chips/</link><description>&lt;p style="margin: 0; padding: 0;"&gt;Over the past year I’ve been on a small adventure to find some excellent Fish &amp;amp; Chips in Toronto, Canada. While I haven’t found the perfect chip shop I have gone to a handful of the most popular places and would like to share what I learned.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;I’ve been to quite a few restaurants, but I’m only going to discuss the ones that stand out. I don’t think you will be disappointed by any location on this list.&lt;/p&gt;&lt;h2 class="_1" id="locations" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/05/03/toronto-fish-chips/#locations" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Locations&lt;/a&gt;&lt;/h2&gt;&lt;h3 class="_1" id="len-duckworth-fish-chips" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/05/03/toronto-fish-chips/#len-duckworth-fish-chips" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Len Duckworth Fish &amp;amp; Chips&lt;/a&gt;&lt;/h3&gt;&lt;p style="margin: 0; padding: 0;"&gt;&lt;a class="_2" href="https://lenduckworthfishandchips.ca/"&gt;lenduckworthfishandchips.ca&lt;/a&gt; - Last Visited 2025&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;Thanks to &lt;a class="_2" href="https://noc.social/@kazaii"&gt;Mark Prosser&lt;/a&gt; who sent in this recommendation after the initial list post went out. This one had managed to escape my searching but I eagerly gave it a try.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The fish was excellent, I tried the halibut as they claim it to be their specialty, and it did not disappoint.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The chips were also stellar, if I really nitpick they could have been slightly crispier, but they were great as they are with a crisp outside and fluffy inside.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;We shared the Sumptuous Seafood platter and everything on it was top notch. The shrimp was super flavourful and while I rarely enjoy battered scallops these were really good. I would strongly recommend this platter which fairly easily serves two. We also got a side of calamari which was also amazing with some sort of curry mayo dip. To top it off we got the pumpkin pie which was also delicious.&lt;/p&gt;&lt;h3 class="_1" id="olde-yorke-fish-chips" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/05/03/toronto-fish-chips/#olde-yorke-fish-chips" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Olde Yorke Fish &amp;amp; Chips&lt;/a&gt;&lt;/h3&gt;&lt;p style="margin: 0; padding: 0;"&gt;&lt;a class="_2" href="https://oldeyorkefishandchips.com/"&gt;oldeyorkefishandchips.com&lt;/a&gt; - Last visited 2025&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;This is probably the location that I have seen recommended the most. And for good reason! This place serves probably the best battered fish I have ever had. We tried the Cod and Haddock, both were great. The cod was more flavourful, but the haddock was closer to a classic white flaky fish. I definitely want to go back and try the halibut.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The chips however were a bit mediocre. Not bad by and stretch, but when it says “chips” above your door I expect better. The outside was a touch tougher than I would like, and the inside wasn’t fluffy like it should be.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;We also tried onion rings and a deep-fried Mars bar which were both fantastic. (I don’t know if they allow subbing in onion rings for the chips but if they do, I would recommend that.)&lt;/p&gt;&lt;h3 class="_1" id="mcnies-fish-chips" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/05/03/toronto-fish-chips/#mcnies-fish-chips" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;McNies Fish &amp;amp; Chips&lt;/a&gt;&lt;/h3&gt;&lt;p style="margin: 0; padding: 0;"&gt;&lt;a class="_2" href="https://mcnies.com/"&gt;mcnies.com&lt;/a&gt; - Last visited 2024&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;This is an old classic of mine as I practiced karate next door for many years. It seems to mostly fly under the radar online, but I think it deserves to be better known. The highlight here is definitely the chips. They are just perfect with crispy outside and fluffy inside.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;The fish however isn’t as good as it could be. The batter in particular is a bit breadier than I would like as opposed to being super light and crispy.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;Other things I have tried are the onion rings, which are possibly my favourite ever (I’m super picky about rings) and the scallops which I wouldn’t recommend (but I don’t think battered scallops are my cup of tea).&lt;/p&gt;&lt;h3 class="_1" id="sea-witch-fish-chips" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/05/03/toronto-fish-chips/#sea-witch-fish-chips" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Sea Witch Fish &amp;amp; Chips&lt;/a&gt;&lt;/h3&gt;&lt;p style="margin: 0; padding: 0;"&gt;&lt;a class="_2" href="https://www.seawitchfc.com/"&gt;seawitchfc.com&lt;/a&gt; - Last visited 2024&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;This is also an online favourite. One of the easiest to get to as it is just a streetcar or short walk from the 1 subway line. They also have an awesome mural on the side of the building, so it is hard to miss.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;While I enjoyed my meal here it did not stand out in any way. Solid fish and chips that I would definitely go to often if I lived nearby, but if you are going out of your way I would probably try one of the other options first.&lt;/p&gt;&lt;h2 class="_1" id="overall" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/05/03/toronto-fish-chips/#overall" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Overall&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;I prefer Len Duckworth’s overall. The fish may not have quite been as good as Olde York but it was still excellent and everything else was top of the charts. It is also just a short walk from Main Street Station which makes it very easy to get to from basically anywhere in Toronto. Second place goes to Olde York, the fish is just that good, and I can get great chips elsewhere. McNie’s still has a place in my heart and I would probably go from-time to time if it wasn’t so far away from where I live. While Sea Witch is solid all around I think I would only go back if I was in the area, if I am going out of my way I would probably find something new first.&lt;/p&gt;&lt;h2 class="_1" id="others" style="margin: .4em 0 0; padding: 0;"&gt;&lt;a href="https://kevincox.ca/2025/05/03/toronto-fish-chips/#others" style="color: inherit!important; margin: 0; padding: 0; text-decoration: inherit;"&gt;Others?&lt;/a&gt;&lt;/h2&gt;&lt;p style="margin: 0; padding: 0;"&gt;Did I miss your favourite? Let me know and I’ll update the list after some thorough research!&lt;/p&gt;</description><author>Kevin Cox's Blog</author><pubDate>Thu, 22 May 2025 22:30:00 GMT</pubDate><guid isPermaLink="true">https://kevincox.ca/2025/05/03/toronto-fish-chips/</guid></item><item><title>Debugging memory leaks in Postgres, heaptrack edition</title><link>http://notes.eatonphil.com/2025-05-22-debugging-memory-leaks-postgres-heaptrack-edition.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/debugging-memory-leaks-postgres-heaptrack-edition"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 22 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-05-22-debugging-memory-leaks-postgres-heaptrack-edition.html</guid></item><item><title>Arities as pseudo-protocol</title><link>https://blog.fogus.me/clojure/arities-as-proto/</link><description>Whereby I talk about an under-appreciated technique in Clojure where function arities serve as a pseudo-protocol...</description><author>Send More Paramedics</author><pubDate>Mon, 19 May 2025 14:44:49 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/clojure/arities-as-proto/</guid></item><item><title>Kubernetes at the (homelab) Edge</title><link>https://www.petekeen.net/kubernetes-at-the-homelab-edge/</link><description>&lt;p&gt;As I've mentioned before, the RF environment in my house is difficult.
        The house layout is roughly:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;single level house, half of which has a basement under it&lt;/li&gt;
        &lt;li&gt;single level office / mother-in-law-suite / ADU / whatever you want it&lt;/li&gt;
        &lt;li&gt;two car garage in between&lt;/li&gt;
        &lt;li&gt;shed in the back yard&lt;/li&gt;
        &lt;/ul&gt;
        
        &lt;p&gt;The house and ADU are built out of cinder block and brick on the outside and plasterboard (not drywall) on the inside with foil-backed insulation sandwiched in between.
        The garage is built in between the two buildings, half with the same cinder block and brick construction and half with more modern stick construction.&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="Diagram of my backyard fiber project" class="thumbnail" src="https://www.petekeen.net/images/backyard_fiber/conduit.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;These buildings were built in the 1950s when labor was cheap, longevity was valued, and AM radio stations were extremely powerful.&lt;/p&gt;
        &lt;p&gt;The shed is just an ordinary stick and OSB sheathing shed, but it's quite a distance from the house proper.&lt;/p&gt;
        &lt;p&gt;I consider the three buildings and the garage as separate &amp;quot;RF zones&amp;quot;, for lack of a better word.
        Zigbee and Wi-Fi at 2.4GHz and Wi-Fi 5 at 5GHz do not propagate through the block and foil very well or at all.
        Z-Wave (900MHz) has a slightly better time but because the house is so spread out and Z-Wave has a fairly low hop limit for mesh packets (4 hops, vs at least 15 for Zigbee) repeaters don't work very well.
        Lutron Caseta (434MHz) has phenominal range and penetrates the foil with zero issues, but the device variety is severely limited.
        In particular, there are no Caseta smart locks.&lt;/p&gt;
        &lt;p&gt;Each zone has:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;At least one Wi-Fi access point&lt;/li&gt;
        &lt;li&gt;A Z-Wave hub&lt;/li&gt;
        &lt;li&gt;(sometimes) a Zigbee hub&lt;/li&gt;
        &lt;li&gt;(sometimes) RS232 or RS485 to USB converters for equipment like our generator and furnace&lt;/li&gt;
        &lt;/ul&gt;
        &lt;h2 id="previous-solutions" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-at-the-homelab-edge/#previous-solutions"&gt;Previous Solutions&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;The Z-Wave and Zigbee &amp;quot;hubs&amp;quot; are mostly just USB sticks stuck into a free port on a Dell Wyse 3040 thin client.
        I've had these little machines running for almost five years through a few different setups.&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="a Dell Wyse 3040 hanging on a wall with a PoE splitter hanging next to it" class="thumbnail" src="https://www.petekeen.net/images/kubernetes-at-the-homelab-edge/garage-wyse-3040.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;First, I had &lt;code&gt;ser2net&lt;/code&gt; running over Tailscale and had the gateway software, i.e. zwave-js-ui and zigbee2mqtt, running on a server in my house.
        This was fine, until I had some significant clock skew when a node rebooted (i.e. the CMOS battery was dead and the hunk of junk thought it was 2016) and Tailscale refused to start because it thought the SSL certificate for the Tailscale control plane wasn't valid.&lt;/p&gt;
        &lt;p&gt;The second draft was to just run the gateway software directly on the 3040s.
        This actually works fine.
        The 3040s are capable machines, roughly comparable to a Raspberry Pi 4, so they could run a little javascript program just fine.
        It was somewhat less responsive than running the gateway on the server, though.&lt;/p&gt;
        &lt;p&gt;The third version of this is to use a hardware gateway.
        I'm currently using one for Zigbee because the location of the house zone's 3040 is not ideal for some important Zigbee devices and they lose connection a lot.
        I positioned the hardware gateway in a spot that has good Wi-Fi coverage but no Ethernet port and now those devices are rock solid.&lt;/p&gt;
        &lt;h2 id="but-what-if-kubernetes%3F" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-at-the-homelab-edge/#but-what-if-kubernetes%3F"&gt;But what if Kubernetes?&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;While rolling out Kubernetes I didn't really plan on converting the 3040s because running the gateway software on them was working fine. Just as an experiment I attempted to install Talos on one of my spares.
        Amazingly, it worked great after making one tweak to the install image.
        The 3040s are very particular about certain things and they, like many of the SBCs that Talos supports, don't like the swanky console dashboard.
        After turning that off the machine came right up as a Kubernetes node in the cluster.&lt;/p&gt;
        &lt;p&gt;At idle the Kubernetes workloads plus my cluster's standard DaemonSet pods use about 40% of the machine's 2GiB of memory and roughly 30% of CPU.&lt;/p&gt;
        &lt;div style="width: 100%;"&gt;&lt;img alt="talosctl dashboard viewing one of my Dell Wyse 3040s" src="https://www.petekeen.net/images/kubernetes-at-the-homelab-edge/wyse-3040-talosctl-dashboard.png" style="width: 100%;" /&gt;&lt;/div&gt;
        &lt;p&gt;That leaves way more than enough to run &lt;code&gt;ser2net&lt;/code&gt;.&lt;/p&gt;
        &lt;h2 id="automatic-ser2net-config" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-at-the-homelab-edge/#automatic-ser2net-config"&gt;Automatic ser2net Config&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I initially thought that I would use Akri to spawn ser2net.
        Akri is a project that came out of Microsoft that acts as a generic device plugin for Kubernetes as well as managing what they call &amp;quot;brokers&amp;quot;, which are just programs that attach to whatever device and provide it to the cluster.&lt;/p&gt;
        &lt;p&gt;That sounded perfect for my purposes so I set it up and let it bake for a few days.
        It did not go well.&lt;/p&gt;
        &lt;p&gt;The big problem is that Akri is just not very stable.
        Things were randomly falling over in such a way that the Akri-managed Z-Wave ser2net brokers would crash loop overnight.
        I made no debugging progress so I started on my fallback idea: automatically managing a ser2net config.&lt;/p&gt;
        &lt;p&gt;Realistically, my needs are simple:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;I want to present one or more serial devices to the network.&lt;/li&gt;
        &lt;li&gt;I want this to be reliable.&lt;/li&gt;
        &lt;li&gt;I want this to be secure.&lt;/li&gt;
        &lt;li&gt;I don't want to micromanage it.&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;It turns out, discovering USB serial devices on linux is actually pretty trivial.
        You just have to follow a bunch of symlinks.&lt;/p&gt;
        &lt;p&gt;This shell script is based on logic found in the &lt;a href="https://github.com/bugst/go-serial/blob/master/enumerator/usb_linux.go#L36"&gt;&lt;code&gt;go-serial&lt;/code&gt;&lt;/a&gt; project:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-sh"&gt;set -e
        set -x
        
        # Find all of the TTYs with names we might be interested in
        ttys=$(ls /sys/class/tty | egrep '(ttyS|ttyHS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO|ttymxc)[0-9]{1,3}')
        
        for tty in $ttys; do
        # follow the symlink to find the real device
        realDevice=$(readlink -f /sys/class/tty/$tty/device)
        
        # what subsystem is it?
        subsystem=$(basename $(readlink -f $realDevice/subsystem))
        
        # locate the directory where the usb information is
        usbdir=&amp;quot;&amp;quot;
        if [ &amp;quot;$subsystem&amp;quot; = &amp;quot;usb-serial&amp;quot; ]; then
        # usb-serial is two levels up from the tty
        usbdir=$(dirname $(dirname $realDevice))
        elif [ &amp;quot;$subsystem&amp;quot; = &amp;quot;usb&amp;quot; ]; then
        # regular usb is one level up from the tty
        usbdir=$(dirname $realDevice)
        else
        # we don't care about this device
        continue
        fi
        
        # read the productId and vendorId attributes from the USB device
        productId=$(cat $usbdir/idProduct)
        vendorId=$(cat $usbdir/idVendor)
        
        snippetFile=&amp;quot;$vendorId:$productId.yaml&amp;quot;
        
        if [ -f &amp;quot;$snippetFile&amp;quot; ]; then
        sed &amp;quot;s/DEVNODE/\/dev\/$tty/&amp;quot; $snippetFile
        fi
        done
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;The last few lines of the loop body look for a YAML snippet available for the specific vendorId/productId pair.
        If there is one, replace the constant &lt;code&gt;DEVNODE&lt;/code&gt; with the actual device path and write it to &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;Here's what one of the snippets looks like:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-yaml"&gt;# zigbee
        
        connection: &amp;amp;zigbee
        accepter: tcp,6639
        connector: serialdev,DEVNODE,115200n81,local,dtr=off,rts=off
        options:
        kickolduser: true
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;This snippet tells ser2net to open a listening connection on TCP port 6639 and wire it to a serial device at path &lt;code&gt;DEVNODE&lt;/code&gt;.
        Use 115200n81 parity and keep the dtr and rts bits off (this is specific to the Sonoff zigbee stick I'm using).
        Further, when a new connection opens immediately kick off the old one.&lt;/p&gt;
        &lt;p&gt;I drop the above script, the YAML snippets, and this simple &lt;code&gt;entrypoint.sh&lt;/code&gt; script into a &lt;a href="https://github.com/peterkeen/ser2net-auto"&gt;container image based on the standard ser2net container&lt;/a&gt;.&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-sh"&gt;#!/bin/bash
        
        set -e
        set -x
        set -o pipefail
        
        echo &amp;quot;Generating ser2net.yaml&amp;quot;
        
        mkdir -p /etc/ser2net
        ./discover.sh &amp;gt; /etc/ser2net/ser2net.yaml
        
        echo &amp;quot;Running ser2net&amp;quot;
        
        cat /etc/ser2net/ser2net.yaml
        
        exec ser2net -d -l -c /etc/ser2net/ser2net.yaml
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;I then deploy it to my cluster as a DaemonSet targeting nodes labeled with &lt;code&gt;keen.land/serials=true&lt;/code&gt;:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-yaml"&gt;apiVersion: apps/v1
        kind: DaemonSet
        metadata:
        name: ser2net
        namespace: iot-system
        labels:
        app: ser2net
        spec:
        selector:
        matchLabels:
        app: ser2net
        template:
        metadata:
        labels:
        app: ser2net
        spec:
        nodeSelector:
        keen.land/serials: &amp;quot;true&amp;quot;
        volumes:
        - name: devices
        hostPath:
        path: /dev
        imagePullSecrets:
        - name: ghcr.io
        containers:
        - name: ser2net
        image: &amp;quot;ghcr.io/peterkeen/ser2net-auto:main&amp;quot;
        securityContext:
        privileged: true
        volumeMounts:
        - name: devices
        mountPath: /dev
        restartPolicy: Always
        hostNetwork: true
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;The interesting things here are that the pod is running in privileged mode on the host network.
        I thought about using container ports but then I would have to somehow know what DaemonSet pod name mapped to which host.
        With &lt;code&gt;hostNetwork: true&lt;/code&gt; I don't have to think about that and can just use the host's name in my gateway configs.
        There's an opportunity here to cook up a custom deployment type with something like &lt;a href="https://metacontroller.github.io/metacontroller/intro.html"&gt;Metacontroller&lt;/a&gt;, which I have installed in the cluster but as of yet haven't done anything with.&lt;/p&gt;
        &lt;h2 id="pros-and-cons" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-at-the-homelab-edge/#pros-and-cons"&gt;Pros and Cons&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;So all of this work and what do I have?
        Basically what I had when I started:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;ser2net on the devices with the serial ports&lt;/li&gt;
        &lt;li&gt;gateway software running on a server&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;The big pro of this setup is that I have one consistent management interface.
        I can set up and tear down ser2net with the exact same interface I use to set up and tear down everything else in the cluster.&lt;/p&gt;
        &lt;p&gt;There are a couple of cons, too.
        First, this probably uses a touch more power than the old solution because in addition to ser2net the Wyse 3040s are running all of the Kubernetes infrastructure.
        These things use so little power as is that I don't think it really matters, but it's worth pointing out.&lt;/p&gt;
        &lt;p&gt;Second, there's more to go wrong.
        Before this I had Alpine running basically nothing except ser2net.
        The system was static in practice, meaning that there was very little that could break.&lt;/p&gt;
        &lt;p&gt;Now there are several components running all the time that could break with a bad upgrade and could require me to take a crash cart out to each machine.&lt;/p&gt;
        &lt;p&gt;This is also putting more stress on the drives.
        All of these machines are booting off of significantly overprovisioned high endurance SD cards now so that shouldn't be an issue, but it's still something to keep in mind.
        The nice thing is that they're entirely stateless so swapping the card and reinstalling should be a quick operation.&lt;/p&gt;
        &lt;p&gt;Ultimately I think this is a good move and I plan to continue down the path of making every non-laptop device run on Kubernetes with very few exceptions.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Mon, 19 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/kubernetes-at-the-homelab-edge/</guid></item><item><title>Gemini Figured Out My Nephew’s Name</title><link>https://blog.nawaz.org/posts/2025/May/gemini-figured-out-my-nephews-name/</link><description>&lt;p&gt;I wrote an &lt;a class="reference external" href="https://modelcontextprotocol.io/introduction"&gt;&lt;span class="caps"&gt;MCP&lt;/span&gt;&lt;/a&gt; server
to give LLMs read-only access to my emails. Here is the chat&amp;nbsp;log.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;ME&lt;/span&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I need to know Donovan&amp;#8217;s son&amp;#8217;s name. It is buried in some email
somewhere. Devise a strategy for finding it. Do &lt;span class="caps"&gt;NOT&lt;/span&gt; use any tools
till we&amp;#8217;ve discussed …&lt;/p&gt;&lt;/blockquote&gt;</description><author>Beetle Space</author><pubDate>Sat, 17 May 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/May/gemini-figured-out-my-nephews-name/</guid></item><item><title>The two kinds of technology minimalists</title><link>https://jodavaho.io/posts/rabbits-or-forth.html</link><description>&lt;p&gt;If you were to take all technologists and instill in them a values of
simplicity and re-use and longevity, and an obsession with repurposing old
tech, I think you&amp;rsquo;d find they roughly sort into two camps:&lt;/p&gt;
&lt;p&gt;One would take on an intellectual journey of discovery, sailing off into the
pacific to learn to live off grid, writing colorful narrative-driven articles,
full of artwork, mission statements, and subtle suggestions of donations. They
would emphasize the human nature of discovery and seek to engage you as they
went. Come to the enlightened tech path! Some neat new technology would be
built along the way around these principles, mostly for their own use.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://100r.co/site/projects.html"&gt;https://100r.co/site/projects.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But the other group would write a self-hosting Forth targeting the top 10 most
used CPU platforms of all time and post a text-only HTML site with download
links. It would work everywhere. You&amp;rsquo;d know nothing about them and they&amp;rsquo;d
probably run linux on a 17 year old mac.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://duskos.org/"&gt;https://duskos.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Personally, I&amp;rsquo;m squarely in the second camp, but I wish dearly I had the
creativity and spine to be in the first camp.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;But your blog is styled!&amp;rdquo; Yeah, well that was all AI, see!?&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Sat, 17 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/rabbits-or-forth.html</guid></item><item><title>CLI computation with GNU datamash</title><link>https://learnbyexample.github.io/cli-computation-gnu-datamash/</link><description>&lt;p&gt;I'm hoping this post will serve as a quick reference for some of the use cases and tickle your curiosity if you haven't come across this nifty CLI text processing tool yet. There are also links for further reading at the end.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation-and-documentation"&gt;Installation and Documentation&lt;a class="zola-anchor" href="#installation-and-documentation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See &lt;a href="https://www.gnu.org/software/datamash/download/"&gt;download&lt;/a&gt; page for source code and instructions to install the software on various platforms. This blog post is based on the &lt;strong&gt;1.8&lt;/strong&gt; version.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://www.gnu.org/software/datamash/manual/"&gt;datamash manual&lt;/a&gt; for links to documentation in HTML, plain text, PDF, etc.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="sum"&gt;Sum&lt;a class="zola-anchor" href="#sum"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# file with a single number per line
&lt;/span&gt;&lt;span&gt;$ cat nums.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10101
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;75
&lt;/span&gt;&lt;span&gt;$ datamash sum &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;nums.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10062.86
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'3.14 42 1000 -51' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; tr &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' ' '\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; datamash sum &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;994.14
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# summing a particular column
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# tab is the default field separator
&lt;/span&gt;&lt;span&gt;$ cat table.txt
&lt;/span&gt;&lt;span&gt;brown bread mat hair &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;blue cake mug shirt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;7
&lt;/span&gt;&lt;span&gt;yellow banana window shoes &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt; sum &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;table.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;38.14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other such operations include &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, &lt;code&gt;mean&lt;/code&gt;, &lt;code&gt;median&lt;/code&gt;, &lt;code&gt;sstdev&lt;/code&gt; (standard deviation), etc.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="transpose-and-reverse"&gt;Transpose and Reverse&lt;a class="zola-anchor" href="#transpose-and-reverse"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat scores.csv
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Maths&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Physics&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Chemistry
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Ith&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Cy&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;97&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;98&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;95
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Lin&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;78&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;83&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;80
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Er&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;60&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;70&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;90
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# interchange rows and columns
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;t, transpose &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;scores.csv
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Ith&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Cy&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Lin&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Er
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Maths&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;97&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;78&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;60
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Physics&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;98&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;83&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;70
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Chemistry&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;95&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;80&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;90
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# reverse columns
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;t, reverse &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;scores.csv
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Chemistry&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Physics&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Maths&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Ith
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;95&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;98&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;97&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Cy
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;80&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;83&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;78&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Lin
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;90&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;70&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;60&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Er
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="group-by"&gt;Group by&lt;a class="zola-anchor" href="#group-by"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use the &lt;code&gt;-g&lt;/code&gt; option to group items based on one or more columns. You can specify an operation such as &lt;code&gt;collapse&lt;/code&gt;, &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;mean&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt; and so on. See &lt;a href="https://unix.stackexchange.com/q/779049/109046"&gt;Grouping rows by categories avoiding repetition&lt;/a&gt; for an example with &lt;code&gt;unique&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# here, the first column items are already next to each other
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# so, sorting is not needed
&lt;/span&gt;&lt;span&gt;$ cat toys.txt
&lt;/span&gt;&lt;span&gt;car blue
&lt;/span&gt;&lt;span&gt;car red
&lt;/span&gt;&lt;span&gt;car yellow
&lt;/span&gt;&lt;span&gt;truck brown
&lt;/span&gt;&lt;span&gt;bus green
&lt;/span&gt;&lt;span&gt;bus maroon
&lt;/span&gt;&lt;span&gt;rocket white
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# by default a comma is used as the separator between collapsed items
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use 'unique' instead of 'collapse' to avoid duplicates
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' ' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;g1 collapse &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;toys.txt
&lt;/span&gt;&lt;span&gt;car blue,red,yellow
&lt;/span&gt;&lt;span&gt;truck brown
&lt;/span&gt;&lt;span&gt;bus green,maroon
&lt;/span&gt;&lt;span&gt;rocket white
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 'count' gives the number of items for the collapsed row
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 'rand' selects a random item for such collapsed rows
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 'first' and 'last' are other choices available
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' ' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;g1 count &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;rand &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;toys.txt
&lt;/span&gt;&lt;span&gt;car &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt; red
&lt;/span&gt;&lt;span&gt;truck &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt; brown
&lt;/span&gt;&lt;span&gt;bus &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt; green
&lt;/span&gt;&lt;span&gt;rocket &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt; white
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's an example with header lines as well as having to sort the input (&lt;code&gt;-s&lt;/code&gt;). The &lt;code&gt;-c&lt;/code&gt; option helps to customize the separator for the grouped items. The &lt;code&gt;-H&lt;/code&gt; option is equivalent to using both &lt;code&gt;--header-in&lt;/code&gt; and &lt;code&gt;--header-out&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;$ cat books.txt
&lt;/span&gt;&lt;span&gt;Author,Title
&lt;/span&gt;&lt;span&gt;Will Wight,Cradle
&lt;/span&gt;&lt;span&gt;John Bierce,Mage Errant
&lt;/span&gt;&lt;span&gt;Brandon Sanderson,Mistborn
&lt;/span&gt;&lt;span&gt;Domagoj Kurmaic,Mother of Learning
&lt;/span&gt;&lt;span&gt;Brandon Sanderson,The Stormlight Archive
&lt;/span&gt;&lt;span&gt;Will Wight,The Last Horizon
&lt;/span&gt;&lt;span&gt;Brandon Sanderson,Warbreaker
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# not sure if there's an option to retain the original header line as is
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# you can instead use: (sed -u 1q; datamash -st, -c: -g1 collapse 2) &amp;lt;books.txt
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use --header-in if you don't want the header line in the output
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;H &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;st, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;c: &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;g1 collapse &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;books.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;GroupBy&lt;/span&gt;&lt;span&gt;(Author),&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;collapse&lt;/span&gt;&lt;span&gt;(Title)
&lt;/span&gt;&lt;span&gt;Brandon Sanderson,Mistborn:The Stormlight Archive:Warbreaker
&lt;/span&gt;&lt;span&gt;Domagoj Kurmaic,Mother of Learning
&lt;/span&gt;&lt;span&gt;John Bierce,Mage Errant
&lt;/span&gt;&lt;span&gt;Will Wight,Cradle:The Last Horizon
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's an example of summing values based on column 3 items:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat duplicates.csv
&lt;/span&gt;&lt;span&gt;brown,toy,bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;dark red,ruby,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;111
&lt;/span&gt;&lt;span&gt;blue,ruby,water,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;dark red,sky,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;555
&lt;/span&gt;&lt;span&gt;yellow,toy,flower,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;white,sky,bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;111
&lt;/span&gt;&lt;span&gt;light red,purse,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;st, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;g3 sum &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;duplicates.csv
&lt;/span&gt;&lt;span&gt;bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;153
&lt;/span&gt;&lt;span&gt;flower,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;999
&lt;/span&gt;&lt;span&gt;water,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Average marks:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat result.csv
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Amy&lt;/span&gt;&lt;span&gt;,maths,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;90
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Amy&lt;/span&gt;&lt;span&gt;,physics,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;75
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Joe&lt;/span&gt;&lt;span&gt;,maths,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;79
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;John&lt;/span&gt;&lt;span&gt;,chemistry,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;77
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;John&lt;/span&gt;&lt;span&gt;,physics,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;91
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe&lt;/span&gt;&lt;span&gt;,maths,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Ravi&lt;/span&gt;&lt;span&gt;,physics,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;84
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Ravi&lt;/span&gt;&lt;span&gt;,chemistry,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;70
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Yui&lt;/span&gt;&lt;span&gt;,maths,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ datamash &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;t, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;g1 mean &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;result.csv
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Amy&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;82.5
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Joe&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;79
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;John&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;84
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Ravi&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;77
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Yui&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="further-reading"&gt;Further Reading&lt;a class="zola-anchor" href="#further-reading"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/datamash/alternatives/"&gt;Alternative one-liners&lt;/a&gt; — examples with datamash compared to &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;perl&lt;/code&gt;, etc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/datamash/examples/"&gt;Documentation examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BurntSushi/xsv"&gt;xsv&lt;/a&gt; — fast CSV command line toolkit&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Fri, 16 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/cli-computation-gnu-datamash/</guid></item><item><title>2025 Advice to my old selves</title><link>https://www.swyx.io/2025-advice</link><description>&lt;p&gt;I turned a birthday recently and it was so busy (DataCouncil + sg flight) that i never really got the chance to sit and reflect. a lot of things are going well, lots more could be better. I'm the only person responsible for preserving the good and fixing the bad. I'm now back on a plane again and on the right mood/medication to write with honesty, sternness and lowered barriers.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 15 May 2025 19:32:56 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/2025-advice</guid></item><item><title>the penultimate conditional syntax</title><link>https://dotat.at/@/2025-05-13-if-is.html</link><description>&lt;p&gt;About half a year ago I &lt;a href="https://lobste.rs/s/tfxw8x/ultimate_conditional_syntax"&gt;encountered&lt;/a&gt; a paper bombastically
titled &lt;a href="https://dl.acm.org/doi/10.1145/3689746"&gt;“the ultimate conditional syntax”&lt;/a&gt;. It has the
attractive goal of unifying pattern &lt;code&gt;match&lt;/code&gt; with boolean &lt;code&gt;if&lt;/code&gt; tests,
and its solution is in some ways very nice. But it seems
over-complicated to me, especially for something that’s a basic
work-horse of programming.&lt;/p&gt;
&lt;p&gt;I couldn’t immediately see how to cut it down to manageable
proportions, but after reading &lt;a href="https://blog.yoshuawuyts.com/syntactic-musings-on-match-expressions/"&gt;syntactic musings on match expressions
by Yoshua Wuyts&lt;/a&gt; a couple of weeks ago, I had an idea. I’ll
outline it under the “penultimate conditionals” heading below, after
reviewing the UCS and explaining my motivation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#what-the-ucs-"&gt;what the UCS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#whence-ucs"&gt;whence UCS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#out-of-scope"&gt;out of scope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#penultimate-conditionals"&gt;penultimate conditionals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#dangling-syntax"&gt;dangling syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#examples"&gt;examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#antepenultimate-breath"&gt;antepenultimate breath&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#what-the-ucs-" name="what-the-ucs-"&gt;what the UCS?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The ultimate conditional syntax does several things which are somewhat
intertwined and support each other.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An “&lt;em&gt;expression&lt;/em&gt; &lt;code&gt;is&lt;/code&gt; &lt;em&gt;pattern&lt;/em&gt;” operator allows you to do pattern
matching inside boolean expressions.&lt;/p&gt;
&lt;p&gt;Like “&lt;code&gt;match&lt;/code&gt;” but unlike most other expressions, “&lt;code&gt;is&lt;/code&gt;” binds
variables whose scope is the rest of the boolean expression that
might be evaluated when the “&lt;code&gt;is&lt;/code&gt;” is true, and the consequent
“&lt;code&gt;then&lt;/code&gt;” clause.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can “split” tests to avoid repeating parts that are the same
in successive branches. For example,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if num &amp;lt; 0 then -1 else
    if num &amp;gt; 0 then +1 else 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;can be written&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if num &amp;lt; 0 then -1
           &amp;gt; 0 then +1
               else  0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example shows a split before an operator, where the left hand
operand is the same and the rest of the expression varies. You can
split after the operator when the operator is the same, which is
common for “&lt;code&gt;is&lt;/code&gt;” pattern match clauses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Indentation-based syntax (an offside rule) reduces the amount of
punctuation that splits would otherwise need. An explicit version
of the example above is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if { x { { &amp;lt; { 0 then −1 } };
             { &amp;gt; { 0 then +1 } }; else 0 } }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(This example is written in the paper on one line. I’ve split it
for narrow screens, which exposes what I think is a mistake in the
nesting.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can also intersperse &lt;code&gt;let&lt;/code&gt; bindings between splits. I doubt
the value of this feature, since “&lt;code&gt;is&lt;/code&gt;” can also bind values, but
interspersed &lt;code&gt;let&lt;/code&gt; does have its uses. The paper has an example
using &lt;code&gt;let&lt;/code&gt; to avoid rightward drift:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if  let tp1_n = normalize(tp1)
        tp1_n is Bot then Bot
        let tp2_n = normalize(tp2)
        tp2_n is Bot then Bot
        let m = merge(tp1_n, tp2_n)
        m is Some(tp) then tp
        m is None then glb(tp1_n, tp2_n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s probably better to use early return to avoid rightward drift.&lt;/p&gt;
&lt;p&gt;The desugaring uses &lt;code&gt;let&lt;/code&gt; bindings when lowering the UCS to
simpler constructions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#whence-ucs" name="whence-ucs"&gt;whence UCS&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pattern matching in the tradition of functional programming languages
supports nested patterns that are compiled in a way that eliminates
redundant tests. For example, this example checks that &lt;code&gt;e1&lt;/code&gt; &lt;code&gt;is&lt;/code&gt;
&lt;code&gt;Some(_)&lt;/code&gt; once, not twice as written.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if e1 is Some(Left(lv)) then e2
             Some(Right(rv)) then e3
             None then e4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Being cheeky, I’d say UCS introduces more causes of redundant checks,
then goes to great effort to to eliminate redundant checks again.&lt;/p&gt;
&lt;p&gt;Splits reduce redundant code at the source level; the bulk of &lt;a href="https://dl.acm.org/doi/10.1145/3689746"&gt;the
paper&lt;/a&gt; is about eliminating redundant checks in the lowering
from source to core language.&lt;/p&gt;
&lt;p&gt;I think the primary cause of this extra complexity is treating the
&lt;code&gt;is&lt;/code&gt; operator as a two-way test rather than a multi-way match. Splits
are introduced as a more general (more complicated) way to build
multi-way conditions out of two-way tests.&lt;/p&gt;
&lt;p&gt;There’s a secondary cause: the tradition of expression-oriented
functional languages doesn’t like early returns. A nice pattern in
imperative code is to write a function as a series of preliminary
calculations and guards with early returns that set things up for the
main work of the function. Rust’s &lt;code&gt;?&lt;/code&gt; operator and &lt;code&gt;let&lt;/code&gt;-&lt;code&gt;else&lt;/code&gt;
statement support this pattern directly. UCS addresses the same
pattern by wedging calculate-check sequences into &lt;code&gt;if&lt;/code&gt; statements, as
in the &lt;code&gt;normalize&lt;/code&gt; example above.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#out-of-scope" name="out-of-scope"&gt;out of scope&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I suspect UCS’s indentation-based syntax will make programmers more
likely to make mistakes, and make compilers have more trouble
producing nice error messages. (YAML has put me off syntax that
doesn’t have enough redundancy to support good error recovery.)&lt;/p&gt;
&lt;p&gt;So I wondered if there’s a way to have something like an “&lt;code&gt;is&lt;/code&gt;
&lt;em&gt;pattern&lt;/em&gt;” operator in a Rust-like language, without an offside rule,
and without the excess of punctuation in the UCS desugaring.&lt;/p&gt;
&lt;p&gt;But I couldn’t work out how to make the scope of variable bindings in
patterns cover all the code that might need to use them. The scope
needs to extend into the consequent &lt;code&gt;then&lt;/code&gt; clause, but also into any
follow-up tests – and those tests can branch so the scope might need
to reach into multiple &lt;code&gt;then&lt;/code&gt; clauses.&lt;/p&gt;
&lt;p&gt;The problem was the way I was still thinking of the &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt;
clauses as part of the outer &lt;code&gt;if&lt;/code&gt;. That implied the expression has to
be closed off before the &lt;code&gt;then&lt;/code&gt;, which troublesomely closes off the
scope of any &lt;code&gt;is&lt;/code&gt;-bound variables. The solution – part of it, at
least – is actually in the paper, where &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; are nested
inside the conditional expression.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#penultimate-conditionals" name="penultimate-conditionals"&gt;penultimate conditionals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are two ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; clauses become operators that cause early
return from a conditional expression.&lt;/p&gt;
&lt;p&gt;They can be lowered to a vaguely Rust syntax with the following
desugaring rules. The &lt;code&gt;'if&lt;/code&gt; label denotes the closest-enclosing
&lt;code&gt;if&lt;/code&gt;; you can’t use &lt;code&gt;then&lt;/code&gt; or &lt;code&gt;else&lt;/code&gt; inside the &lt;em&gt;&lt;code&gt;expr&lt;/code&gt;&lt;/em&gt; of a
&lt;code&gt;then&lt;/code&gt; or &lt;code&gt;else&lt;/code&gt; unless there’s another intervening &lt;code&gt;if&lt;/code&gt;.
&lt;br /&gt;
&lt;span style="padding: 0 1em;"&gt; &lt;/span&gt;
&lt;code&gt;then &lt;/code&gt; &lt;em&gt;&lt;code&gt;expr&lt;/code&gt;&lt;/em&gt;
&lt;span style="font-size: 2em; padding: 0 0.5em;"&gt;⟼&lt;/span&gt;
&lt;code&gt;&amp;amp;&amp;amp; break 'if &lt;/code&gt; &lt;em&gt;&lt;code&gt;expr&lt;/code&gt;&lt;/em&gt;
&lt;br /&gt;
&lt;span style="padding: 0 1em;"&gt; &lt;/span&gt;
&lt;code&gt;else &lt;/code&gt; &lt;em&gt;&lt;code&gt;expr&lt;/code&gt;&lt;/em&gt;
&lt;span style="font-size: 2em; padding: 0 0.5em;"&gt;⟼&lt;/span&gt;
&lt;code&gt;|| break 'if &lt;/code&gt; &lt;em&gt;&lt;code&gt;expr&lt;/code&gt;&lt;/em&gt;
&lt;br /&gt;
&lt;span style="padding: 0 1em;"&gt; &lt;/span&gt;
&lt;code&gt;else &lt;/code&gt; &lt;em&gt;&lt;code&gt;expr&lt;/code&gt;&lt;/em&gt;
&lt;span style="font-size: 2em; padding: 0 0.5em;"&gt;⟼&lt;/span&gt;
&lt;code&gt;|| _ &amp;amp;&amp;amp; break 'if &lt;/code&gt; &lt;em&gt;&lt;code&gt;expr&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are two desugarings for &lt;code&gt;else&lt;/code&gt; depending on whether it
appears in an expression or a pattern.&lt;/p&gt;
&lt;p&gt;If you prefer a less wordy syntax, you might spell &lt;code&gt;then&lt;/code&gt; as &lt;code&gt;=&amp;gt;&lt;/code&gt;
(like &lt;code&gt;match&lt;/code&gt; in Rust) and &lt;code&gt;else&lt;/code&gt; as &lt;code&gt;|| =&amp;gt;&lt;/code&gt;. (For symmetry we
might allow &lt;code&gt;&amp;amp;&amp;amp; =&amp;gt;&lt;/code&gt; for &lt;code&gt;then&lt;/code&gt; as well.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An &lt;code&gt;is&lt;/code&gt; operator for multi-way pattern-matching that binds
variables whose scope covers the consequent part of the
expression.&lt;/p&gt;
&lt;p&gt;The basic form is like the UCS,&lt;/p&gt;
&lt;p&gt;&lt;span style="padding: 0 1em;"&gt; &lt;/span&gt;
&lt;tt&gt;&lt;em&gt;scrutinee&lt;/em&gt; is &lt;em&gt;pattern&lt;/em&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;which matches the &lt;em&gt;&lt;code&gt;scrutinee&lt;/code&gt;&lt;/em&gt; against the &lt;em&gt;&lt;code&gt;pattern&lt;/code&gt;&lt;/em&gt; returning
a boolean result. For example,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    foo is None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Guarded patterns are like,&lt;/p&gt;
&lt;p&gt;&lt;span style="padding: 0 1em;"&gt; &lt;/span&gt;
&lt;tt&gt;&lt;em&gt;scrutinee&lt;/em&gt; is  &lt;em&gt;pattern&lt;/em&gt; &amp;amp;&amp;amp; &lt;em&gt;consequent&lt;/em&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;where the scope of the variables bound by the &lt;em&gt;&lt;code&gt;pattern&lt;/code&gt;&lt;/em&gt; covers
the &lt;em&gt;&lt;code&gt;consequent&lt;/code&gt;&lt;/em&gt;. The &lt;em&gt;&lt;code&gt;consequent&lt;/code&gt;&lt;/em&gt; might be a simple boolean
guard, for example,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    foo is Some(n) &amp;amp;&amp;amp; n &amp;lt; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or inside an &lt;code&gt;if&lt;/code&gt; expression it might end with a &lt;code&gt;then&lt;/code&gt; clause,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if foo is Some(n) &amp;amp;&amp;amp; n &amp;lt; 0 =&amp;gt; -1
    // ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple multi-way patterns are like,&lt;/p&gt;
&lt;p&gt;&lt;span style="padding: 0 1em;"&gt; &lt;/span&gt;
&lt;tt&gt;&lt;em&gt;scrutinee&lt;/em&gt; is { &lt;em&gt;pattern&lt;/em&gt; || &lt;em&gt;pattern&lt;/em&gt; || … }&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;If there is a &lt;em&gt;&lt;code&gt;consequent&lt;/code&gt;&lt;/em&gt; then the patterns must all bind the
same set of variables (if any) with the same types.&lt;/p&gt;
&lt;p&gt;More typically, a multi-way match will have &lt;em&gt;&lt;code&gt;consequent&lt;/code&gt;&lt;/em&gt;
clauses, like&lt;/p&gt;
&lt;p&gt;&lt;span style="padding: 0 1em;"&gt; &lt;/span&gt;
&lt;tt&gt;&lt;em&gt;scrutinee&lt;/em&gt; is {&lt;br /&gt;
&lt;span style="padding: 0 2em;"&gt; &lt;/span&gt;
&lt;em&gt;pattern&lt;/em&gt; &amp;amp;&amp;amp; &lt;em&gt;consequent&lt;/em&gt; ||&lt;br /&gt;
&lt;span style="padding: 0 2em;"&gt; &lt;/span&gt;
&lt;em&gt;pattern&lt;/em&gt; &amp;amp;&amp;amp; &lt;em&gt;consequent&lt;/em&gt; ||&lt;br /&gt;
&lt;span style="padding: 0 2em;"&gt; &lt;/span&gt;
=&amp;gt; &lt;em&gt;otherwise&lt;/em&gt; }&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;When a &lt;em&gt;&lt;code&gt;consequent&lt;/code&gt;&lt;/em&gt; is false, we go on to try other alternatives
of the match, like we would when the first operand of boolean &lt;code&gt;||&lt;/code&gt;
is false.&lt;/p&gt;
&lt;p&gt;To help with layout, you can include a redundant &lt;code&gt;||&lt;/code&gt; before the
first alternative.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if foo is {
        || Some(n) &amp;amp;&amp;amp; n &amp;lt; 0 =&amp;gt; -1
        || Some(n) &amp;amp;&amp;amp; n &amp;gt; 0 =&amp;gt; +1
        || Some(n) =&amp;gt; 0
        || None =&amp;gt; 0
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if foo is { Some(n) &amp;amp;&amp;amp; ( n &amp;lt; 0 =&amp;gt; -1 ||
                             n &amp;gt; 0 =&amp;gt; +1 ||
                             =&amp;gt; 0 )
             || None =&amp;gt; 0 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(They should compile the same way.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The evaluation model is like familiar shortcutting &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; and
the syntax is supposed to reinforce that intuition. The UCS paper
spends a lot of time discussing backtracking and how to eliminate it,
but penultimate conditionals evaluate straightforwardly from left to
right.&lt;/p&gt;
&lt;p&gt;The paper briefly mentions &lt;code&gt;as&lt;/code&gt; patterns, like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Some(Pair(x, y) as p)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which in Rust would be written&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Some(p @ Pair(x, y))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;is&lt;/code&gt; operator doesn’t need a separate syntax for this feature:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Some(p is Pair(x, y))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For large examples, the penultimate conditional syntax is about as
noisy as Rust’s &lt;code&gt;match&lt;/code&gt;, but it scales down nicely to smaller matches.
However, there are differences in how consequences and alternatives
are punctuated which need a bit more discussion.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#dangling-syntax" name="dangling-syntax"&gt;dangling syntax&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The precedence and associativity of the &lt;code&gt;is&lt;/code&gt; operator is tricky: it
has two kinds of dangling-else problem.&lt;/p&gt;
&lt;p&gt;The first kind occurs with a surrounding boolean expression. For
example, when &lt;code&gt;b = false&lt;/code&gt;, what is the value of this?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    b is true || false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It could bracket to the left, yielding &lt;code&gt;false&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    (b is true) || false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or to the right, yielding &lt;code&gt;true&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    b is { true || false }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This could be disambiguated by using different spellings for boolean
or and pattern alternatives. But that doesn’t help for the second kind
which occurs with an inner match.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    foo is Some(_) &amp;amp;&amp;amp; bar is Some(_) || None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Does that check &lt;code&gt;foo is Some(_)&lt;/code&gt; with an always-true look at &lt;code&gt;bar&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ( foo is Some(_) ) &amp;amp;&amp;amp; bar is { Some(_) || None }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or does it check &lt;code&gt;bar is Some(_)&lt;/code&gt; and waste time with &lt;code&gt;foo&lt;/code&gt;?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    foo is { Some(_) &amp;amp;&amp;amp; ( bar is Some(_) ) || None }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have chosen to resolve the ambiguity by requiring curly braces &lt;code&gt;{}&lt;/code&gt;
around groups of alternative patterns. This allows me to use the same
spelling &lt;code&gt;||&lt;/code&gt; for all kinds of alternation. (Compare Rust, which uses
&lt;code&gt;||&lt;/code&gt; for boolean expressions, &lt;code&gt;|&lt;/code&gt; in a pattern, and &lt;code&gt;,&lt;/code&gt; between the
arms of a &lt;code&gt;match&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Curlies around multi-way matches can be nested, so the example in the
previous section can also be written,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if foo is {
        || Some(n) &amp;amp;&amp;amp; n &amp;lt; 0 =&amp;gt; -1
        || Some(n) &amp;amp;&amp;amp; n &amp;gt; 0 =&amp;gt; +1
        || { Some(0) || None } =&amp;gt; 0
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;is&lt;/code&gt; operator binds tigher than &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; on its left, but looser than
&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; on its right (so that a chain of &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; is gathered into a
&lt;em&gt;&lt;code&gt;consequent&lt;/code&gt;&lt;/em&gt;) and tigher than &lt;code&gt;||&lt;/code&gt; on its right so that outer &lt;code&gt;||&lt;/code&gt;
alternatives don’t need extra brackets.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#examples" name="examples"&gt;examples&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’m going to finish these notes by going through &lt;a href="https://dl.acm.org/doi/10.1145/3689746"&gt;the ultimate
conditional syntax paper&lt;/a&gt; to translate most of its examples
into the penultimate syntax, to give it some exercise.&lt;/p&gt;
&lt;p&gt;Here we use &lt;code&gt;is&lt;/code&gt; to name a value &lt;code&gt;n&lt;/code&gt;, as a replacement for the &lt;code&gt;|&amp;gt; abs&lt;/code&gt; pipe operator, and we use range patterns instead of split
relational operators:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if foo(args) is {
        || 0 =&amp;gt; "null"
        || n &amp;amp;&amp;amp; abs(n) is {
            || 101.. =&amp;gt; "large"
            || ..10 =&amp;gt; "small"
            || =&amp;gt; "medium"
        )
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In both the previous example and the next one, we have some extra
brackets where UCS relies purely on an offside rule.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if x is {
        || Right(None) =&amp;gt; defaultValue
        || Right(Some(cached)) =&amp;gt; f(cached)
        || Left(input) &amp;amp;&amp;amp; compute(input) is {
            || None =&amp;gt; defaultValue
            || Some(result) =&amp;gt; f(result)
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one is almost identical to UCS apart from the spellings of &lt;code&gt;and&lt;/code&gt;,
&lt;code&gt;then&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if name.startsWith("_")
    &amp;amp;&amp;amp; name.tailOption is Some(namePostfix)
    &amp;amp;&amp;amp; namePostfix.toIntOption is Some(index)
    &amp;amp;&amp;amp; 0 &amp;lt;= index &amp;amp;&amp;amp; index &amp;lt; arity
    &amp;amp;&amp;amp; =&amp;gt; Right([index, name])
    || =&amp;gt; Left("invalid identifier: " + name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are some nested multi-way matches with overlapping patterns and
bound values:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if e is {
        // ...
        || Lit(value)
            &amp;amp;&amp;amp; Map.find_opt(value) is Some(result)
            =&amp;gt; Some(result)
        // ...
        || { Lit(value) ||
             Add(Lit(0), value) ||
             Add(value, Lit(0)) }
            =&amp;gt; {
                print_int(value);
                Some(value)
            }
        // ...
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next few examples show UCS splits without the &lt;code&gt;is&lt;/code&gt; operator. In my
syntax I need to press a few more buttons but I think that’s OK.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if x == 0 =&amp;gt; "zero"
    || x == 1 =&amp;gt; "unit"
    || =&amp;gt; "?"

    if x == 0 =&amp;gt; "null"
    || x &amp;gt; 0 =&amp;gt; "positive"
    || =&amp;gt; "negative"

    if predicate(0, 1) =&amp;gt; "A"
    || predicate(2, 3) =&amp;gt; "B"
    || =&amp;gt; "C"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first two can be written with &lt;code&gt;is&lt;/code&gt; instead, but it’s not briefer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if x is {
        || 0 =&amp;gt; "zero"
        || 1 =&amp;gt; "unit"
        || =&amp;gt; "?"
    }

    if x is {
        || 0 =&amp;gt; "null"
        || 1.. =&amp;gt; "positive"
        || =&amp;gt; "negative"
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There’s little need for a split-anything feature when we have
multi-way matches.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if foo(u, v, w) is {
        || Some(x) &amp;amp;&amp;amp; x is {
            || Left(_) =&amp;gt; "left-defined"
            || Right(_) =&amp;gt; "right-defined"
        }
        || None =&amp;gt; "undefined"
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A more complete function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    fn zip_with(f, xs, ys) {
        if [xs, ys] is {
            || [x :: xs, y :: ys]
                &amp;amp;&amp;amp; zip_with(f, xs, ys) is Some(tail)
                =&amp;gt; Some(f(x, y) :: tail)
            || [Nil, Nil] =&amp;gt; Some(Nil)
            || =&amp;gt; None
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another fragment of the expression evaluator:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if e is {
        // ...
        || Var(name) &amp;amp;&amp;amp; Map.find_opt(env, name) is {
            || Some(Right(value)) =&amp;gt; Some(value)
            || Some(Left(thunk)) =&amp;gt; Some(thunk())
        }
        || App(lhs, rhs) =&amp;gt; // ...
        // ...
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This expression is used in the paper to show how a UCS split is
desugared:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if Pair(x, y) is {
        || Pair(Some(xv), Some(yv)) =&amp;gt; xv + yv
        || Pair(Some(xv), None) =&amp;gt; xv
        || Pair(None, Some(yv)) =&amp;gt; yv
        || Pair(None, None) =&amp;gt; 0
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The desugaring in the paper introduces a lot of redundant tests. I
would desugar straightforwardly, then rely on later optimizations to
eliminate other redundancies such as the construction and immediate
destruction of the pair:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if Pair(x, y) is Pair(xx, yy) &amp;amp;&amp;amp; xx is {
        || Some(xv) &amp;amp;&amp;amp; yy is {
            || Some(yv) =&amp;gt; xv + yv
            || None =&amp;gt; xv
        }
        || None &amp;amp;&amp;amp; yy is {
            || Some(yv) =&amp;gt; yv
            || None =&amp;gt; 0
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Skipping ahead to the “non-trivial example” in the paper’s fig. 11:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if e is {
        || Var(x) &amp;amp;&amp;amp; context.get(x) is {
            || Some(IntVal(v)) =&amp;gt; Left(v)
            || Some(BoolVal(v)) =&amp;gt; Right(v)
        }
        || Lit(IntVal(v)) =&amp;gt; Left(v)
        || Lit(BoolVal(v)) =&amp;gt; Right(v)
        // ...
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next example in the paper compares C# relational patterns. Rust’s
range patterns do a similar job, with the caveat that Rust’s ranges
don’t have a syntax for exclusive lower bounds.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    fn classify(value) {
        if value is {
            || .. -4.0 =&amp;gt; "too low"
            || 10.0 .. =&amp;gt; "too high"
            || NaN =&amp;gt; "unknown"
            || =&amp;gt; "acceptable"
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tend to think relational patterns are the better syntax than ranges.
With relational patterns I can rewrite an earlier example like,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if foo is {
        || Some(&amp;lt; 0) =&amp;gt; -1
        || Some(&amp;gt; 0) =&amp;gt; +1
        || { Some(0) || None } =&amp;gt; 0
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think with the UCS I would have to name the &lt;code&gt;Some(_)&lt;/code&gt; value to be
able to compare it, which suggests that relational patterns can be
better than UCS split relational operators.&lt;/p&gt;
&lt;p&gt;Prefix-unary relational operators are also a nice way to write
single-ended ranges in expressions. We could simply write both ends to
get a complete range, like &lt;code&gt;&amp;gt;= lo &amp;lt; hi&lt;/code&gt; or like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if value is &amp;gt; -4.0 &amp;lt; 10.0 =&amp;gt; "acceptable"
    || =&amp;gt; "far out"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Near the start I quoted a &lt;code&gt;normalize&lt;/code&gt; example that illustrates
left-aligned UCS expression. The penultimate version drifts right like
the Scala version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if normalize(tp1) is {
        || Bot =&amp;gt; Bot
        || tp1_n &amp;amp;&amp;amp; normalize(tp2) is {
            || Bot =&amp;gt; Bot
            || tp2_n &amp;amp;&amp;amp; merge(tp1_n, tp2_n) is {
                || Some(tp) =&amp;gt; tp
                || None =&amp;gt; glb(tp1_n, tp2_n)
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But a more Rusty style shows the benefits of early returns (especially
the terse &lt;code&gt;?&lt;/code&gt; operator) and monadic combinators.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let tp1 = normalize(tp1)?;
    let tp2 = normalize(tp2)?;
    merge(tp1, tp2)
        .unwrap_or_else(|| glb(tp1, tp2))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-05-13-if-is.html#antepenultimate-breath" name="antepenultimate-breath"&gt;antepenultimate breath&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I started writing these notes, my penultimate conditional syntax
was little more than a sketch of an idea. Having gone through the
previous section’s exercise, I think it has turned out better than I
thought it might.&lt;/p&gt;
&lt;p&gt;The extra nesting from multi-way match braces doesn’t seem to be
unbearably heavyweight. However, none of the examples have bulky
&lt;code&gt;then&lt;/code&gt; or &lt;code&gt;else&lt;/code&gt; blocks which are where the extra nesting is more
likely to be annoying. But then, as I said before it’s comparable to a
Rust &lt;code&gt;match&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    match scrutinee {
        pattern =&amp;gt; {
            consequent
        }
    }

    if scrutinee is {
        || pattern =&amp;gt; {
            consequent
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;||&lt;/code&gt; lines down the left margin are noisy, but hard to get rid of
in the context of a curly-brace language. I can’t reduce them to &lt;code&gt;|&lt;/code&gt;
like OCaml because what would I use for bitwise OR? I don’t want
presence or absence of flow control to depend on types or context. I
kind of like Prolog / Erlang &lt;code&gt;,&lt;/code&gt; for &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;;&lt;/code&gt; for &lt;code&gt;||&lt;/code&gt;, but that’s
well outside what’s legible to mainstream programmers. So, dunno.&lt;/p&gt;
&lt;p&gt;Anyway, I think I’ve successfully found a syntax that does most of
what UCS does, but much in a much simpler fashion.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Wed, 14 May 2025 00:49:41 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-05-13-if-is.html</guid></item><item><title>PDF to Text, a challenging problem</title><link>https://www.marginalia.nu/log/a_119_pdf/</link><description>&lt;p&gt;The search engine has recently gained the ability to index the PDF file format. The change will deploy over a few months.&lt;/p&gt;
&lt;p&gt;Extracting text information from PDFs is a significantly bigger challenge than it might seem.
The crux of the problem is that the file format isn&amp;rsquo;t a text format at all, but a graphical format.&lt;/p&gt;
&lt;p&gt;It doesn&amp;rsquo;t have text in the way you might think of it, but more of a mapping of glyphs to coordinates on &amp;ldquo;paper&amp;rdquo;. These
glyphs may be rotated, overlap, and appear out of order, with very little semantic information
attached to them.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 13 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_119_pdf/</guid></item><item><title>Stress And Programming</title><link>https://thecodist.com/stress-and-programming/</link><description>&lt;p&gt;Having spent four decades as a programmer in various industries and situations, I know that modern software development processes are far more stressful than when I started.&lt;/p&gt;&lt;p&gt;It&amp;apos;s not simply that developing software today is more complex than it was back in 1981. In that early decade, none&lt;/p&gt;</description><author>The Codist</author><pubDate>Fri, 09 May 2025 21:04:03 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/stress-and-programming/</guid></item><item><title>Switching to Kubernetes</title><link>https://www.petekeen.net/kubernetes-homelab/</link><description>&lt;blockquote&gt;
        &lt;p&gt;And you may ask yourself, &amp;quot;How do I work this?&amp;quot;&lt;br /&gt;
        And you may ask yourself, &amp;quot;Where is that large home server?&amp;quot;&lt;/p&gt;
        &lt;/blockquote&gt;
        &lt;p&gt;Once upon a time I had a Mac mini. It was hooked up to the tv (because we only had the one) and it ran Plex. It was fine.&lt;/p&gt;
        &lt;p&gt;Later, my new spouse and I moved across the country into a house.
        I decided that I should get a server because I was going to be a big time consultant and I figured I would need a staging environment.
        A Dell T30 picked up on super sale arrived soon after.&lt;/p&gt;
        &lt;p&gt;The server sat, ignored, while we suffered through the first few years of one baby, then two babies.&lt;/p&gt;
        &lt;p&gt;Later, we moved to our forever house and I found Home Assistant.
        I picked up a Raspberry Pi 4. All was good.&lt;/p&gt;
        &lt;p&gt;Except it kind of sucked?
        A 1GB Pi 4 is pretty limited in what it can practically run.
        Home Assistant ran mostly ok but anything else was beyond it's capabilities.
        To eBay!&lt;/p&gt;
        &lt;h2 id="oooh%2C-shiny-hardware" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#oooh%2C-shiny-hardware"&gt;Oooh, shiny hardware&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;Over the past four years I've accumulated a modest menagerie of hardware:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;Hypnotoad, a HP 800 G3 mini&lt;/li&gt;
        &lt;li&gt;Crushinator, a HP 800 G3 SFF&lt;/li&gt;
        &lt;li&gt;Morbo, another HP 800 G3 SFF&lt;/li&gt;
        &lt;li&gt;Lrrr, a Dell Wyse 5070 thin client&lt;/li&gt;
        &lt;li&gt;Roberto, another Dell Wyse 5070&lt;/li&gt;
        &lt;li&gt;Nibbler, a Lenovo M80S Gen 3 SFF&lt;/li&gt;
        &lt;li&gt;Shed, another Dell Wyse 5070 (such a boring name)&lt;/li&gt;
        &lt;li&gt;A pack of roving Dell Wyse 3040 thin clients&lt;/li&gt;
        &lt;li&gt;The original Pi 4&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;The T30, sadly, imploded when I tried to install a video card and fried the motherboard.
        It's name was Kodos and it was a good box.&lt;/p&gt;
        &lt;h2 id="software%2C-take-1-through-n" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#software%2C-take-1-through-n"&gt;Software, take 1 through N&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;As I was acquiring hardware I was also acquiring software to run on it and developed a somewhat esoteric way of deploying that software.
        The first interesting version was a self-deploying Docker container.
        It would get passed the Docker socket and run compose, deciding on the fly what to deploy based on the hostname of the machine.&lt;/p&gt;
        &lt;p&gt;This was fine, but it proved too much for the 3040s which have fragile 8GB eMMC drives.&lt;/p&gt;
        &lt;p&gt;A later version moved the script to my laptop and used Ansible to push Docker compose files out to all the machines.&lt;/p&gt;
        &lt;p&gt;Fine. Fiddly, but fine.&lt;/p&gt;
        &lt;h2 id="software%2C-take-n-%2B-1" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#software%2C-take-n-%2B-1"&gt;Software, take N + 1&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;&lt;a href="https://xeiaso.net/"&gt;Xe Iaso&lt;/a&gt; is a person that I've been following online for years.
        Recently they went through a &lt;a href="https://xeiaso.net/blog/2024/homelab-v2/"&gt;homelab transformation&lt;/a&gt;, where for Reasons they decided to switch away from NixOS.
        After trying various things, much to everyone's chagrin, they settled on Kubernetes running on Talos.&lt;/p&gt;
        &lt;p&gt;Talos seemed to be what I wanted: an immutable, hardened OS designed for one thing and one thing only: Kubernetes.&lt;/p&gt;
        &lt;p&gt;Much like Xe, I had resisted Kubernetes at home for a long time.
        Too complex. Too much overhead. Just too much.&lt;/p&gt;
        &lt;p&gt;Taking another look at that hardware list, though, I do actually have a somewhat Kubernetes-shaped problem.
        I want to treat my hardware as respected but mostly interchangable pets.&lt;/p&gt;
        &lt;p&gt;My deployment script was sophisticated but had no ability to just put something somewhere else automatically.
        It was entirely static, so when something needed to move I would have to restore a backup to the new target and manually redeploy at least part of the world in order to get the ingress set up properly.&lt;/p&gt;
        &lt;p&gt;Kubernetes takes care of that stuff for me.
        I don't have to think about where any random workload runs and I don't have to think about migrating it somewhere else if the node falls over.
        DNS, SSL certificates, backups, it all just happens in the background.&lt;/p&gt;
        &lt;h2 id="what's-it-look-like%3F" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#what's-it-look-like%3F"&gt;What's it look like?&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;After a couple of weeks of futzing around and day dreaming I settled on this software stack:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;Kubernetes (obvo)&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://www.talos.dev/"&gt;Talos Linux&lt;/a&gt; driven by &lt;a href="https://budimanjojo.github.io/talhelper/latest/"&gt;Talhelper&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;Helm for off the shelf components, driven by &lt;a href="https://github.com/helmfile/helmfile"&gt;Helmfile&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://longhorn.io/"&gt;Longhorn&lt;/a&gt; for fast replicated storage&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://developer.1password.com/docs/k8s/operator/"&gt;1Password Operator&lt;/a&gt; for secrets management&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://tailscale.com/kb/1236/kubernetes-operator"&gt;Tailscale Operator&lt;/a&gt; for private ingress and a subnet router to poke at services and pods directly&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://kubernetes.github.io/ingress-nginx/"&gt;ingress-nginx&lt;/a&gt; for internal and external access to services&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://metallb.io/"&gt;MetalLB&lt;/a&gt; to give local services a stable virtual IP address&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://cert-manager.io/"&gt;cert-manager&lt;/a&gt; for automatic LetsEncrypt certificates for services&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://kubernetes-sigs.github.io/external-dns/v0.15.0/"&gt;external-dns&lt;/a&gt; to drive DNS updates for services&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://keel.sh/"&gt;Keel.sh&lt;/a&gt; for automatic image updates&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://docs.stakater.com/reloader/index.html"&gt;Reloader&lt;/a&gt; to reload deployments when linked secrets and configs update&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://www.emqx.com/en/emqx-kubernetes-operator"&gt;EMQX&lt;/a&gt; as the MQTT server for some of my IoT devices (mostly zigbee)&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://cloudnative-pg.io/"&gt;CloudNative PG&lt;/a&gt; for PostgreSQL databases&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;The next thing to decide was how to divide up the hardware into control plane and worker nodes. Here's what I have so far:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;Three (3) control plane nodes: hypnotoad, crushinator, and lrrr&lt;/li&gt;
        &lt;li&gt;Seven (7) local worker nodes: hypnotoad, crushinator, nibbler, shed, three Wyse 3040s hosting Z-Wave sticks&lt;/li&gt;
        &lt;li&gt;One (1) cloud worker node&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;You might notice that several nodes are doing double duty.&lt;/p&gt;
        &lt;p&gt;Splitting the control plane off to dedicated nodes makes sense when you have a fleet of hundreds of machines in a data center.
        I don't have that.&lt;/p&gt;
        &lt;p&gt;A small VM running on Lrrr is the only dedicated control plane node. The only reason for that is because Lrrr also hosts my Unifi and Omada network controllers and I haven't worked up the gumption to move those from Proxmox LXCs to k8s workloads.&lt;/p&gt;
        &lt;p&gt;Hypnotoad, Crushinator, and Nibbler are general compute.
        Nibbler has an Nvidia Tesla P4 GPU, which is not particularly impressive but fun to play with.
        Both Hypnotoad and Nibbler have iGPUs capable of running many simultaneous Jellyfin streams.
        Crushinator is a VM taking up most of the host which is also serving as a backup NAS for non-media data.&lt;/p&gt;
        &lt;p&gt;Shed lives in the shed and is connected to a bunch of USB devices, including two SDR radios, a Z-Wave stick, and an RS232-to-USB adapter for the generator.&lt;/p&gt;
        &lt;p&gt;Morbo is running TrueNAS and has no connection to Kubernetes except that some stuff running in k8s uses NFS shares.
        It's also the backup target for Longhorn and the script I use to backup Talos' etcd database.&lt;/p&gt;
        &lt;h2 id="self-hosting-in-the-cloud" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#self-hosting-in-the-cloud"&gt;Self-hosting in the Cloud&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;Talos has a neat feature built in that they call &lt;a href="https://www.talos.dev/v1.10/talos-guides/network/kubespan/"&gt;KubeSpan&lt;/a&gt;.
        This is a Wireguard mesh network between all of the nodes in the cluster that uses a hosted discovery service to exchange public keys.&lt;/p&gt;
        &lt;p&gt;Essentially, you can flip a single option in your Talos configs and have all of your nodes meshed, with a bonus option to send &lt;em&gt;all&lt;/em&gt; internal cluster traffic over the Wireguard interface.
        The discovery service never sees private data, just hashes.
        It's really cool.&lt;/p&gt;
        &lt;p&gt;I'm using KubeSpan to put one of my nodes on a VPS to get a public IP without exposing my home ISP connection directly.
        After initial setup I was able to change the firewall to block all inbound ports other than 80, 443, and the KubeSpan UDP port.&lt;/p&gt;
        &lt;p&gt;To actually serve public traffic I installed a separate instance of ingress-nginx that only runs on the cloud node.
        This instance is set up to directly expose the cloud node's public IP which gets picked up by external-dns automatically.&lt;/p&gt;
        &lt;p&gt;I'm still trying to decide if this single node is enough or if I should get really clever and use a proxy running on Fly to get a public anycast IP.&lt;/p&gt;
        &lt;h2 id="ok%2C-but-what's-running%3F" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#ok%2C-but-what's-running%3F"&gt;Ok, but what's running?&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;Learning how Kubernetes works has been great and this process filled in quite a few gaps in my understanding, but it probably wouldn't have been worth the effort without hosting something useful.&lt;/p&gt;
        &lt;p&gt;Currently I'm hosting a couple of external production workloads:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;this blog&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://vmsave.petekeen.net/"&gt;VMSave&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;a handful of very small websites&lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;I'm also running a bunch of homeprod services:&lt;/p&gt;
        &lt;ul&gt;
        &lt;li&gt;&lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://github.com/SYSTRAN/faster-whisper"&gt;Whisper&lt;/a&gt; and &lt;a href="https://github.com/rhasspy/piper"&gt;Piper&lt;/a&gt;, speech-to-text and text-to-speech tools and components of the Home Assistant voice pipeline&lt;/li&gt;
        &lt;li&gt;four (4) instances of &lt;a href="https://zwave-js.github.io/zwave-js-ui//#/"&gt;Z-Wave JS UI&lt;/a&gt;, one per RF &amp;quot;zone&amp;quot; (this house has wacky RF behavior)&lt;/li&gt;
        &lt;li&gt;two (2) instances of &lt;a href="https://www.zigbee2mqtt.io/"&gt;Zigbee2MQTT&lt;/a&gt;, one in each RF zone that has Zigbee devices&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://github.com/jgyates/genmon"&gt;Genmon&lt;/a&gt; keeps tabs on our whole home standby generator&lt;/li&gt;
        &lt;li&gt;A Minecraft server for me and my kids&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://docs.paperless-ngx.com/"&gt;Paperless-ngx&lt;/a&gt; stores and indexes important documents&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder"&gt;Ultrafeeder&lt;/a&gt; puts the planes flying overhead on a map&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://github.com/AirswitchAsa/icloudpd-web"&gt;iCloudPD-web&lt;/a&gt; for effortless iCloud photo backups&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://jellyfin.org/"&gt;Jellyfin&lt;/a&gt;, an indexer and server for TV shows, movies and music&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://sonarr.tv/"&gt;Sonarr&lt;/a&gt;, &lt;a href="https://radarr.video/"&gt;Radarr&lt;/a&gt;, &lt;a href="https://prowlarr.com/"&gt;Prowlarr&lt;/a&gt; and &lt;a href="https://sabnzbd.org/"&gt;SABnzbd&lt;/a&gt; form the core of our media acquisition system&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://docs.jellyseerr.dev/"&gt;Jellyseerr&lt;/a&gt; makes requesting new media easy for the other people who live in the house&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://github.com/crocodilestick/Calibre-Web-Automated"&gt;Calibre Web Automated&lt;/a&gt; is an amazing tool that serves all of my eBooks to my Kobo eReader&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://ollama.com/"&gt;Ollama&lt;/a&gt; and &lt;a href="https://openwebui.com/"&gt;Open Web UI&lt;/a&gt; for dinking around with local LLMs&lt;/li&gt;
        &lt;li&gt;&lt;a href="https://github.com/bastienwirtz/homer"&gt;Homer&lt;/a&gt; to keep track of all of the above, set as my browser homepage&lt;/li&gt;
        &lt;/ul&gt;
        &lt;h2 id="left-to-do" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#left-to-do"&gt;Left To Do&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;There are a few things left on the todo list.
        Roberto is hooked up to a webcam that watches my 3D printer and I haven't touched it yet because it is connected via Wi-Fi which Talos doesn't support at all.&lt;/p&gt;
        &lt;p&gt;I also haven't touched the raspberry pi, mostly for the same reason.
        The pi is serving as a gateway between a Wi-Fi SD card that lives in my CPAP machine and the rest of the network so that I can scrape the data off without having to pull the card or futz with my laptop's Wi-Fi every day.&lt;/p&gt;
        &lt;p&gt;The Wi-Fi SD card, you see, only exists as an access point. It cannot be put into a mode that connects to another network.
        The pi has a USB Wi-Fi adapter connected to the card's network and the built-in Wi-Fi connected to the home network with nginx in between serving as a proxy.
        I don't think this is something that I really want or need to move into k8s.&lt;/p&gt;
        &lt;p&gt;I want to set up some sort of central authn/authz system for the homeprod services.
        The current fashion seems to be &lt;a href="https://pocket-id.org/"&gt;Pocket ID&lt;/a&gt; but I haven't been able to get it working reliably.&lt;/p&gt;
        &lt;p&gt;I'm thinking about setting up a small ActivityPub server to play around with.&lt;/p&gt;
        &lt;p&gt;A photo viewer like &lt;a href="https://immich.app/"&gt;Immich&lt;/a&gt; might be cool to set up.&lt;/p&gt;
        &lt;h2 id="overkill%3F" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/kubernetes-homelab/#overkill%3F"&gt;Overkill?&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;Of course this is overkill. This could probably all live on a single Wyse 5070 with a couple big harddrives attached.&lt;/p&gt;
        &lt;p&gt;I think it's been worth it to use Kubernetes in anger.
        I'm really enjoying the ability to deploy whatever I want to the cluster without having to think about where it runs, where it stores data, etc.&lt;/p&gt;
        &lt;p&gt;I've also learned a ton and fixed a bunch of preconceived notions and it's already helped increase reliability in a few things that affect household acceptance in big ways.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Fri, 09 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/kubernetes-homelab/</guid></item><item><title>Everything you need to know about sed substitution</title><link>https://learnbyexample.github.io/everything-about-sed-substitution/</link><description>&lt;p&gt;The command name &lt;code&gt;sed&lt;/code&gt; is derived from &lt;strong&gt;s&lt;/strong&gt;tream &lt;strong&gt;ed&lt;/strong&gt;itor. The most commonly used editing command is &lt;strong&gt;substitution&lt;/strong&gt;, for which various examples are shown in this blog post.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; The examples presented here have been tested with &lt;code&gt;GNU sed&lt;/code&gt;. Syntax and features might differ for other implementations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="basic-substitution"&gt;Basic Substitution&lt;a class="zola-anchor" href="#basic-substitution"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The substitute command syntax is &lt;code&gt;s/REGEXP/REPLACEMENT/FLAGS&lt;/code&gt; where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; stands for the &lt;strong&gt;substitute&lt;/strong&gt; command&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; is an idiomatic delimiter character to separate various portions of the command&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REGEXP&lt;/code&gt; is the &lt;strong&gt;regular expression&lt;/strong&gt; that defines the search portion&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REPLACEMENT&lt;/code&gt; refers to the replacement string&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FLAGS&lt;/code&gt; are options to change the default behavior of the command&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# for each input line, change only the first ',' to '-'
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4\na,b,c,d\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/,/-/'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;b,c,d
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# you can change all the matches by adding the 'g' flag
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4\na,b,c,d\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/,/=-=/g'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=-=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=-=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=-=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span&gt;a=&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-=&lt;/span&gt;&lt;span&gt;b=&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-=&lt;/span&gt;&lt;span&gt;c=&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-=&lt;/span&gt;&lt;span&gt;d
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="filter-and-substitute"&gt;Filter and Substitute&lt;a class="zola-anchor" href="#filter-and-substitute"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use line numbers, regular expressions or a combination of them to select lines and then apply a command to these filtered lines. See the &lt;a href="https://learnbyexample.github.io/learn_gnused/selective-editing.html"&gt;Selective editing&lt;/a&gt; chapter from my ebook to learn more about the various kinds of addressing.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# make changes only to the first line
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4\na,b,c,d\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1 s/,/::/g'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span&gt;a,b,c,d
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# apply substitution only if the input line does NOT contain '2'
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4\na,b,c,d\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/2/! s/,/-/g'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="regular-expressions"&gt;Regular Expressions&lt;a class="zola-anchor" href="#regular-expressions"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Only a handful of examples are shown here. See &lt;a href="https://learnbyexample.github.io/learn_gnused/breere-regular-expressions.html"&gt;this chapter&lt;/a&gt; from my ebook for a more detailed discussion. See &lt;a href="https://learnbyexample.github.io/gnu-bre-ere-cheatsheet/"&gt;my blog post&lt;/a&gt; for a cheatsheet.&lt;/p&gt;
&lt;p&gt;Anchors:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# lines starting with 'par'
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'car par\nparty\nspare\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/^par/tas/'
&lt;/span&gt;&lt;span&gt;car par
&lt;/span&gt;&lt;span&gt;tasty
&lt;/span&gt;&lt;span&gt;spare
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# words starting with 'par'
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'car par\nparty\nspare\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\bpar/1234/'
&lt;/span&gt;&lt;span&gt;car &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1234
&lt;/span&gt;&lt;span&gt;1234ty
&lt;/span&gt;&lt;span&gt;spare
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternation and Grouping:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# same as: sed -E 's/part|parrot|parent/X/g'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# -E option enables ERE (default is BRE)
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par part parrot parent' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/par(en|ro)?t/X/g'
&lt;/span&gt;&lt;span&gt;par &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;X X X
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Character Class and Quantifiers:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# numbers &amp;gt;= 100 with optional leading zeros
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'0501 035 154 12 26 98234' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\b0*[1-9][0-9]{2,}\b/X/g'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;X &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;035 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;X &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;12 26 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;X
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# retain only punctuation characters
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;',pie tie#ink-eat_42' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/[^[:punct:]]+//g'
&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;#-_
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Backreferences:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# remove two or more duplicate words separated by spaces
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \b prevents false matches like 'the theatre', 'sand and stone' etc
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa a a a 42 f_1 f_1 f_13.14' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\b(\w+)( \1)+\b/\1/g'
&lt;/span&gt;&lt;span&gt;aa a &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42&lt;/span&gt;&lt;span&gt; f_1 f_13.&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;14
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# whole words that have at least one consecutive repeated character
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'effort flee facade oddball rat tool' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\w*(\w)\1\w*/X/g'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;X X&lt;/span&gt;&lt;span&gt; facade &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;X&lt;/span&gt;&lt;span&gt; rat &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;X
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match lowercase followed by underscore followed by lowercase
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# delete the underscore and convert the 2nd lowercase to uppercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'_fig aug_price next_line' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/([a-z])_([a-z])/\1\u\2/g'
&lt;/span&gt;&lt;span&gt;_fig augPrice nextLine
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="replace-specific-occurrences"&gt;Replace Specific Occurrences&lt;a class="zola-anchor" href="#replace-specific-occurrences"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use a &lt;em&gt;number&lt;/em&gt; as a flag to replace only that particular occurrence of the search term. If you combine this with the &lt;code&gt;g&lt;/code&gt; flag, all occurrences after that particular match will also be replaced.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple:banana:cherry:fig:mango'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace only the second occurrence
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/[^:]+/&amp;quot;&amp;amp;&amp;quot;/2'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;apple:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;banana&amp;quot;&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;:cherry:fig:mango
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace all matches except the first occurrence
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/:/---/2g'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;apple:&lt;/span&gt;&lt;span&gt;banana&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---&lt;/span&gt;&lt;span&gt;cherry&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---&lt;/span&gt;&lt;span&gt;fig&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the help of capture groups and backreferences, you can replace a specific occurrence from the end of the input line.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'car,art,pot,map,urn,ray,ear'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace the last occurrence
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/(.*),/\1[]/'
&lt;/span&gt;&lt;span&gt;car,art,pot,map,urn,ray[]ear
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# generic version, where {N} refers to last but Nth occurrence
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/(.*),((.*,){3})/\1[]\2/'
&lt;/span&gt;&lt;span&gt;car,art,pot[]map,urn,ray,ear
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="executing-external-commands"&gt;Executing External Commands&lt;a class="zola-anchor" href="#executing-external-commands"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;e&lt;/code&gt; flag helps to insert the output of a shell command within &lt;code&gt;sed&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# replace the entire line with the output of a shell command
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\nreplace this line\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/^replace.*/date/e'
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Wednesday &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;07 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;May &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2025 10&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;25&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;34 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;AM IST
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# after substitution, the command that gets executed is 'seq 3'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'xyz 3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/xyz/seq/e'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="different-delimiters"&gt;Different Delimiters&lt;a class="zola-anchor" href="#different-delimiters"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;/&lt;/code&gt; character is idiomatically used as the REGEXP delimiter. But any character other than &lt;code&gt;\&lt;/code&gt; and the newline character can be used instead.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# instead of this
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/home/learnbyexample/reports' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\/home\/learnbyexample\//~\//'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;~/&lt;/span&gt;&lt;span&gt;reports
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use a different delimiter
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/home/learnbyexample/reports' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s#/home/learnbyexample/#~/#'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;~/&lt;/span&gt;&lt;span&gt;reports
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="in-place-editing"&gt;In-place Editing&lt;a class="zola-anchor" href="#in-place-editing"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;-i&lt;/code&gt; option is helpful to write back the changes to the original files itself. If you don't provide an argument to this option, backup of the original file won't be created.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat colors.txt
&lt;/span&gt;&lt;span&gt;deep blue
&lt;/span&gt;&lt;span&gt;light orange
&lt;/span&gt;&lt;span&gt;blue delight
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# no output on terminal as the -i option is used
&lt;/span&gt;&lt;span&gt;$ sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;i.bkp &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/blue/green/'&lt;/span&gt;&lt;span&gt; colors.txt
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# output from sed is written back to 'colors.txt'
&lt;/span&gt;&lt;span&gt;$ cat colors.txt
&lt;/span&gt;&lt;span&gt;deep green
&lt;/span&gt;&lt;span&gt;light orange
&lt;/span&gt;&lt;span&gt;green delight
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# original file is preserved in 'colors.txt.bkp'
&lt;/span&gt;&lt;span&gt;$ cat colors.txt.bkp
&lt;/span&gt;&lt;span&gt;deep blue
&lt;/span&gt;&lt;span&gt;light orange
&lt;/span&gt;&lt;span&gt;blue delight
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;*&lt;/code&gt; in the argument to the &lt;code&gt;-i&lt;/code&gt; option will be replaced with the input filename. So, &lt;code&gt;-i'bkp.*'&lt;/code&gt; for &lt;code&gt;f1.txt&lt;/code&gt; will create &lt;code&gt;bkp.f1.txt&lt;/code&gt; as the backup. And if you use &lt;code&gt;old/*&lt;/code&gt;, the backups will be under the same name but under the directory &lt;code&gt;old&lt;/code&gt; (provided that directory already exists).&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="manipulating-newlines"&gt;Manipulating Newlines&lt;a class="zola-anchor" href="#manipulating-newlines"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, &lt;code&gt;sed&lt;/code&gt; reads the input line by line (with &lt;code&gt;\n&lt;/code&gt; considered as the line ending). The newline character, if present, is removed and then added back when the pattern space is printed. Which implies that you cannot directly manipulate the newline character, unless you use features that results in more than one line in the pattern space.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# append the next line to the pattern space
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and then replace newline character with a colon character
&lt;/span&gt;&lt;span&gt;$ seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;7 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'N; s/\n/:/'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;7
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# if line contains 'at', the next line gets appended to the pattern space
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# then the substitution is performed on the two lines in the buffer
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'gates\nnot\nused\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/at/{N; s/s\nnot/d/}'
&lt;/span&gt;&lt;span&gt;gated
&lt;/span&gt;&lt;span&gt;used
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="slurping-input"&gt;Slurping Input&lt;a class="zola-anchor" href="#slurping-input"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If the input doesn't have NUL characters, then the &lt;code&gt;-z&lt;/code&gt; option is handy to process the entire input as a single string. This is effective only for files small enough to fit the available machine memory. It would also depend on the regular expression, as some patterns have exponential relationship with respect to the data size.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# add ; to the previous line if the current line starts with c
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cater\ndog\ncoat\ncutter\nmat\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;z &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\nc/;&amp;amp;/g'
&lt;/span&gt;&lt;span&gt;cater
&lt;/span&gt;&lt;span&gt;dog;
&lt;/span&gt;&lt;span&gt;coat;
&lt;/span&gt;&lt;span&gt;cutter
&lt;/span&gt;&lt;span&gt;mat
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="fixed-string-substitution"&gt;Fixed String Substitution&lt;a class="zola-anchor" href="#fixed-string-substitution"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Typically, you'd need to escape &lt;code&gt;\&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt; and the delimiter for the string used in the replacement section. For the search section, the characters to be escaped will depend upon whether you are using BRE or ERE.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# replacement string
&lt;/span&gt;&lt;span&gt;$ r=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a/b&amp;amp;c\d'
&lt;/span&gt;&lt;span&gt;$ r=$(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%s&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &amp;quot;$r&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s#[\&amp;amp;/]#&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;amp;#g'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# ERE version for the search string
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{[(\ta^b/d).*+?^$|]}'
&lt;/span&gt;&lt;span&gt;$ s=$(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%s&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s#[{[()^$*?+.\|/]#&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;amp;#g'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%s&lt;/span&gt;&lt;span style="color: #d07711;"&gt;\n' 'f*{[(\ta^b/d).*+?^$|]} - 3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/'&amp;quot;$s&amp;quot;'/'&amp;quot;$r&amp;quot;'/g'
&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;c\d &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;- &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# BRE version for the search string
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{[(\ta^b/d).*+?^$|]}'
&lt;/span&gt;&lt;span&gt;$ s=$(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%s&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s#[[^$*.\/]#&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;amp;#g'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%s&lt;/span&gt;&lt;span style="color: #d07711;"&gt;\n' 'f*{[(\ta^b/d).*+?^$|]} - 3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/'&amp;quot;$s&amp;quot;'/'&amp;quot;$r&amp;quot;'/g'
&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;c\d &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;- &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See &lt;a href="https://learnbyexample.github.io/multiline-search-and-replace/"&gt;my blog post&lt;/a&gt; for multiline fixed string substitution examples.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="programming-ebooks"&gt;Programming ebooks&lt;a class="zola-anchor" href="#programming-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href="https://learnbyexample.github.io/books/"&gt;my ebooks&lt;/a&gt; on Regular Expressions, Linux CLI tools, Python and Vim. You can get them all as a single bundle via &lt;a href="https://leanpub.com/b/learnbyexample-all-books"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books"&gt;gumroad&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 07 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/everything-about-sed-substitution/</guid></item><item><title>Building Better SDKs with Generative AI</title><link>https://caseysoftware.com/blog/building-better-sdks-with-generative-ai?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=building-better-sdks-with-generative-ai</link><description>&lt;p&gt;Through many years of working with APIs, I&amp;#8217;ve always been struck by the fundamental problems of SDKs. Most are not&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/building-better-sdks-with-generative-ai"&gt;Building Better SDKs with Generative AI&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Mon, 05 May 2025 16:38:00 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/building-better-sdks-with-generative-ai?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=building-better-sdks-with-generative-ai</guid></item><item><title>Where are the proactive AI coding tools?</title><link>https://austinhenley.com/blog/proactiveai.html</link><description>&lt;a href="https://austinhenley.com/blog/proactiveai.html"&gt;https://austinhenley.com/blog/proactiveai.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Mon, 05 May 2025 04:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/proactiveai.html</guid></item><item><title>Viewing adjacent French towns on Wikipedia</title><link>https://anisse.astier.eu/wikidata-communes-viewer.html</link><description>&lt;p&gt;It all started with this really simple &lt;a href="https://boitam.eu/@joachim/114325424853633229"&gt;nerdsnipe by Joachim (french)&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How many clicks would it take to go from the Wikipedia pages of (southmost Metropolitan France town) Cerbères to (northmost France town) Bray-les-Dunes, using only Wikipedia's "adjacent communes links" ?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quite a simple question. Many surprises.&lt;/p&gt;
&lt;h1&gt;Querying Wikidata&lt;/h1&gt;
&lt;p&gt;So, we …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Mon, 05 May 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/wikidata-communes-viewer.html</guid></item><item><title>Exponentials in 3 Instructions</title><link>https://specbranch.com/posts/fast-exp/</link><description>&lt;p&gt;&lt;em&gt;This post expands on an algorithm shown in the &lt;a href="https://www.routledge.com/9781032933559"&gt;book I wrote&lt;/a&gt;
on floating-point math.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It is very common in computing to want to do $e^x$ very quickly and not care very much about
how accurately you computed it. This is increasingly true in ML and AI algorithms, which can
be very tolerant to noise from numerical error and often use low bit precision either way. It also
shows up doing things like exponentially-weighted moving averages and other similar functions in
signal processing. Thankfully, you can replace $e^x$ in these situations with the following function:&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Sun, 04 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/fast-exp/</guid></item><item><title>White noise into random walks</title><link>https://bytepawn.com/white-noise-into-random-walks.html</link><description>&lt;p&gt;A visual and analytical walk from pure white noise to a true random walk.&lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/white-noise-random-walk-2.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 04 May 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/white-noise-into-random-walks.html</guid></item><item><title>Dead Bird Features</title><link>https://kevincox.ca/2025/05/03/dead-birds/</link><description>&lt;p style="margin: 0; padding: 0;"&gt;Anyone who has had an outdoor cat will have experienced a thoughtful gift left on your doorstep in the morning, usually a dead bird or mouse. These gifts are made with love, and take effort to acquire and return to your doorstep. However, us humans generally do not appreciate them.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;I often find similar features in software. For example, the wiki that our company uses recently released a new feature: when you copy from a document it would format the copied text as Markdown. For example if I copied part of a code snippet inside a second-level list it would be formatted as:&lt;/p&gt;&lt;pre class="_1"&gt;&lt;code class="_2 language-markdown"&gt;&lt;span class="_3"&gt;1&lt;/span&gt;&lt;span class="_4"&gt;.&lt;/span&gt;
    &lt;span class="_3"&gt;1&lt;/span&gt;&lt;span class="_4"&gt;.&lt;/span&gt; &lt;span class="_3"&gt;```&lt;/span&gt;&lt;span class="_5"&gt;bash&lt;/span&gt;
       -funsafe-math-optimizations
       &lt;span class="_3"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p style="margin: 0; padding: 0;"&gt;Whoever implemented this feature clearly worked really hard to ensure that parent context like the two lists above were included in this copying. However ultimately it isn’t what users want. When copying the entire list keeping the formatting could be useful, however when copying just one word the context is typically not desired. In this case the result is that I can’t paste it into my terminal.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;Often these features are technically interesting. It is a fun coding project to take a snippet of the document and turn it into accurate Markdown. It is easy to forget to take a step back and think if the user actually wants this.&lt;/p&gt;&lt;p style="margin: 0; padding: 0;"&gt;I find that these features often occur when overriding default behaviours. Web apps are a common culprit. Overriding copying is a common example, but also overriding spell checking, form filling, link following or undo. Many times there is a key feature that the developer wanted to provide, and they made impressive effort re-implementing browser behaviour. At best the feature is useful, but the reimplemented browser behaviour falls short of native. But very often the feature that was provided isn’t actually even helpful.&lt;/p&gt;</description><author>Kevin Cox's Blog</author><pubDate>Sat, 03 May 2025 15:00:00 GMT</pubDate><guid isPermaLink="true">https://kevincox.ca/2025/05/03/dead-birds/</guid></item><item><title>Perfect Random Floating-Point Numbers</title><link>https://specbranch.com/posts/fp-rand/</link><description>&lt;p&gt;When I recently looked at the state of the art in floating point random number generation,
I was surprised to see a common procedure in many programming languages and libraries that
is not really a floating-point algorithm:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate a random integer with bits chosen based on the precision of the format.&lt;/li&gt;
&lt;li&gt;Convert to floating point.&lt;/li&gt;
&lt;li&gt;Divide to produce an output between 0 and 1.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In code, this looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-go"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Float64&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rand_int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Int63n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rand_int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function is supposed to produce floating-point numbers drawn from a uniform distribution
in the interval $[0, 1)$. Zero is a possible output, but one is not, and the distribution is
uniform. The number &amp;quot;53&amp;quot; in the algorithm above is chosen in a way that is floating-point aware:
the double-precision floating-point numbers have 53 bits of precision, so this algorithm only
creates bits equal to the precision of the number system. It seems to fit the bill.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Sat, 03 May 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/fp-rand/</guid></item><item><title>Driving up an active Volcano in Iceland! Hekkla</title><link>https://theroadchoseme.com/driving-up-an-active-volcano-in-iceland-hekkla</link><description>Driving to the top of an active volcano may not be the smartest thing we&amp;#8217;ve ever done, but it sure is one of the most beautiful! On today&amp;#8217;s adventure we&amp;#8217;re in the southern highlands&amp;#46;&amp;#46;&amp;#46;</description><author>The Road Chose Me</author><pubDate>Thu, 01 May 2025 16:53:34 GMT</pubDate><guid isPermaLink="true">https://theroadchoseme.com/driving-up-an-active-volcano-in-iceland-hekkla</guid></item><item><title>testing data structures per element</title><link>https://dotat.at/@/2025-04-30-test.html</link><description>&lt;p&gt;Recently, &lt;a href="https://matklad.github.io"&gt;Alex Kladov&lt;/a&gt; wrote on the TigerBeetle blog about
&lt;a href="https://tigerbeetle.com/blog/2025-04-23-swarm-testing-data-structures/"&gt;swarm testing data structures&lt;/a&gt;. It’s a neat post about
randomized testing with Zig. I wrote a comment &lt;a href="https://lobste.rs/s/xuaafo/swarm_testing_data_structures#c_dd6d02"&gt;with an idea that was
new to Alex @matklad&lt;/a&gt;, so I’m reposing a longer version
here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#differential-testing"&gt;differential testing&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#problems"&gt;problems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#grow-shrink"&gt;grow / shrink&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#random-elements"&gt;random elements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#element-wise-testing"&gt;element-wise testing&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#test-loop"&gt;test loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#data-structure-size"&gt;data structure size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#invariants"&gt;invariants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#performance"&gt;performance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#conclusion"&gt;conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#differential-testing" name="differential-testing"&gt;differential testing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A common approach to testing data structures is to write a second
reference implementation that has the same API but simpler and/or more
obviously correct, though it uses more memory or is slower or less
concurrent or otherwise not up to production quality.&lt;/p&gt;
&lt;p&gt;Then, run the production implementation and the reference
implementation on the same sequence of operations, and verify that
they produce the same results.&lt;/p&gt;
&lt;p&gt;Any difference is either a bug in the production implementation
(probably) or a bug in the reference implementation (unlucky) or a bug
in the tests (unfortunate).&lt;/p&gt;
&lt;p&gt;This is a straightforward differential testing pattern.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#problems" name="problems"&gt;problems&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are a couple of difficulties with this kind of basic
differential testing.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#grow-shrink" name="grow-shrink"&gt;grow / shrink&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://tigerbeetle.com/blog/2025-04-23-swarm-testing-data-structures/"&gt;The TigerBeetle article&lt;/a&gt; talks about adjusting the
probabilities of different operations on the data structure to try to
explore more edge cases. To motivate the idea, the article talks about
adjusting the probabilities of adding or deleting items: If adding and
deleting have equal probability, then the test finds it hard to grow
the data structure to interesting sizes that might expose bugs.&lt;/p&gt;
&lt;p&gt;Unfortunately, if the probability of &lt;code&gt;add&lt;/code&gt; is greater than &lt;code&gt;del&lt;/code&gt;, then
the data structure tends to grow without bound. If the probability of
&lt;code&gt;del&lt;/code&gt; is greater than &lt;code&gt;add&lt;/code&gt;, then it tries to shrink from nothing:
worse than equal probabilities! They could preload the data structure
to test how it behaves when it shrinks, but a fixed set of
probabilities per run is not good at testing both growth and shrinkage
on the same test run on the same data structure.&lt;/p&gt;
&lt;p&gt;One way to improve this kind of test is to adjust the probability of
&lt;code&gt;add&lt;/code&gt; and &lt;code&gt;del&lt;/code&gt; dynamically: make &lt;code&gt;add&lt;/code&gt; more likely when the data
structure is small, and &lt;code&gt;del&lt;/code&gt; more likely when it is big. And maybe
make &lt;code&gt;add&lt;/code&gt; more likely in the first half of a test run and &lt;code&gt;del&lt;/code&gt; more
likely in the second half.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#random-elements" name="random-elements"&gt;random elements&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://tigerbeetle.com/blog/2025-04-23-swarm-testing-data-structures/"&gt;The TigerBeetle article&lt;/a&gt; glosses over the question of where
the tests get fresh elements to add to the data structure. And its
example is chosen so it doesn’t have to think about which elements get
deleted.&lt;/p&gt;
&lt;p&gt;In my experience writing data structures for non-garbage-collected
languages, I had to be more deliberate about how to create and destroy
elements. That led to a style of test that’s more element-centric, as
Alex described it.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#element-wise-testing" name="element-wise-testing"&gt;element-wise testing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Change the emphasis so that instead of testing that two
implementations match, test that one implementation obeys the expected
behaviour. No need to make a drop-in replacement reference
implementation!&lt;/p&gt;
&lt;p&gt;What I typically do is pre-allocate an array of elements, with slots
that I can set to keep track of how each element relates to the data
structure under test. The most important property is whether the
element has been added or deleted, but there might be others related
to ordering of elements, or values associated with keys, and so on.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#test-loop" name="test-loop"&gt;test loop&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Each time round the loop, choose at random an element from the array,
and an action such as add / del / get / …&lt;/p&gt;
&lt;p&gt;Then, if it makes sense, perform the operation on the data structure
with the element.&lt;/p&gt;
&lt;p&gt;For example, you might skip an add action if the element is already in
the data structure, unless you can try to add it and expect an error.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#data-structure-size" name="data-structure-size"&gt;data structure size&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This strategy tends to grow the data structure until about 50% of the
pre-allocated elements are inserted, then it makes a random walk
around this 50% point. Random walks can diverge widely from their
central point both in theory and in practice, so this kind of testing
is reasonably effective at both growing and (to a lesser extent)
shrinking the data structure.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#invariants" name="invariants"&gt;invariants&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I usually check some preconditions before an action, to verify that
the data structure matches the expected properties of the chosen
element. This can help to detect earlier that an action on one element
has corrupted another element.&lt;/p&gt;
&lt;p&gt;After performing the action and updating the element’s properties, I
check the updated properties as a postcondition, to make sure the
action had the expected effects.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#performance" name="performance"&gt;performance&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;John Regehr’s great tutorial, &lt;a href="https://blog.regehr.org/archives/896"&gt;how to fuzz an ADT
implementation&lt;/a&gt;, recommends writing a &lt;code&gt;checkRep()&lt;/code&gt; function
that thoroughly verifies a data structure’s internal consistency. A
&lt;code&gt;checkRep()&lt;/code&gt; function is a solid gold testing tool, but it is O(n) at
least and typically very slow.&lt;/p&gt;
&lt;p&gt;If you call &lt;code&gt;checkRep()&lt;/code&gt; frequently during testing, your tests slow
down dramatically as your data structure gets larger.&lt;/p&gt;
&lt;p&gt;I like my per-element invariants to be local and ideally O(1) or
O(log n) at worst, so they don’t slow down the tests too much.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://dotat.at/@/2025-04-30-test.html#conclusion" name="conclusion"&gt;conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recently I’ve used this pattern to exhibit concurrency bugs in an API
that’s hard to make thread-safe. Writing the tests has required some
cunning to work out what invariants I can usefully maintain and test;
what variety of actions I can use to stress those invariants; and what
mix of elements + actions I need so that my tests know which properties
of each element should be upheld and which can change.&lt;/p&gt;
&lt;p&gt;I’m testing multiple implementations of the same API, trying to
demonstrate which is safest. Differential testing can tell me that
implementations diverge, but not which is correct, whereas testing
properties and invariants more directly tells me whether an
implementation does what I expect. (Or gives me a useless answer when
my tests are weak.)&lt;/p&gt;
&lt;p&gt;Which is to say that this kind of testing is a fun creative challenge.
I find it a lot more rewarding than example-based testing.&lt;/p&gt;</description><author>Tony Finch's blog</author><pubDate>Thu, 01 May 2025 04:25:16 GMT</pubDate><guid isPermaLink="true">https://dotat.at/@/2025-04-30-test.html</guid></item><item><title>The design of software engineering course projects</title><link>https://austinhenley.com/blog/groupprojects.html</link><description>&lt;a href="https://austinhenley.com/blog/groupprojects.html"&gt;https://austinhenley.com/blog/groupprojects.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Tue, 29 Apr 2025 01:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/groupprojects.html</guid></item><item><title>My Plan, Document, Act, Review flow for Agentic Software Development</title><link>https://smcleod.net/2025/04/my-plan-document-act-review-flow-for-agentic-software-development/</link><description>An overview of my agentic coding development flow.</description><author>smcleod.net</author><pubDate>Sun, 27 Apr 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/04/my-plan-document-act-review-flow-for-agentic-software-development/</guid></item><item><title>Random Walks and the Dickey-Fuller Test - Part II</title><link>https://bytepawn.com/random-walks-the-dickey-fuller-test-part-ii.html</link><description>&lt;p&gt;The article examines how simple autoregressive stochastic processes behave with the Dickey-Fuller test.&lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/adf-1.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 26 Apr 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/random-walks-the-dickey-fuller-test-part-ii.html</guid></item><item><title>LLM Sampling Parameters Guide</title><link>https://smcleod.net/2025/04/llm-sampling-parameters-guide/</link><description>A practical guide to LLM sampling parameters for Ollama, llama.cpp, and MLX</description><author>smcleod.net</author><pubDate>Thu, 24 Apr 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/04/llm-sampling-parameters-guide/</guid></item><item><title>An Appearance on Apropos</title><link>https://blog.fogus.me/2025/04/23/apropos-me/</link><description>I was fortunate to appear on the April 22, 2025 episode of the Apropos podcast...</description><author>Send More Paramedics</author><pubDate>Wed, 23 Apr 2025 22:44:49 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/2025/04/23/apropos-me/</guid></item><item><title>Debugging A Crawler Stall</title><link>https://www.marginalia.nu/log/a_118_crawler_stall/</link><description>&lt;p&gt;Some time ago, I migrated the crawler off the okhttp library, to use
Java&amp;rsquo;s builtin HTTP client. This seemed like a good idea at the time,
but has led to a fair number of headaches.&lt;/p&gt;
&lt;p&gt;Java&amp;rsquo;s HttpClient has one damning flaw, and that that it doesn&amp;rsquo;t support socket timeouts.&lt;/p&gt;
&lt;p&gt;Its only supported timeout values are time to connect, and time until first byte of the response. This means the client can get stuck on a read call if a server stops responding, potentially for a very long time!&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 22 Apr 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_118_crawler_stall/</guid></item><item><title>Screw your title</title><link>http://notes.eatonphil.com/2025-04-22-burn-your-title.html</link><description>&lt;p&gt;I've been a developer, a manager, a cofounder, and now I'm a developer
again. I ran away from each position until being a founder because I
felt like I was limited by what I was allowed to do.&lt;/p&gt;
&lt;p&gt;But I reached an enlightenment of sorts during my career progression:
that everyone around me was dying for someone to pick things up, for
employees to show engagement and agency.&lt;/p&gt;
&lt;p&gt;We think of our titles as our limits. We're quick to say and believe,
"that isn't my job". While in reality titles reflect the minimum
expected of us, not the maximum that is open to us.&lt;/p&gt;
&lt;h3 id="moving-your-career-forward"&gt;Moving your career forward&lt;/h3&gt;&lt;p&gt;Trying to figure out what (new minimum) you must do to get promoted
seems kind of backwards to me, reinforcing our sense of our own
limits. Instead, at every stage in your career, focus on doing the
intersection of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what you see needs to be done (that isn't being done)&lt;/li&gt;
&lt;li&gt;what you are capable of doing&lt;/li&gt;
&lt;li&gt;what you have the desire/energy (or would find fulfillment) doing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And this is the path to promotion and a successful and interesting
career.&lt;/p&gt;
&lt;p&gt;Screw your title. screw your job description. I mean, keep your boss
happy for sure. Keep your teammates happy by supporting them and
building them up and communicating well. But don't wait to be
officially made a lead or given a new title to do what otherwise fits
into that intersection above.&lt;/p&gt;
&lt;p&gt;And if after doing this for some time, demonstrating this level of
agency, you are not promoted, it just means you're not at the right
company or right organization within your company and you should look
elsewhere.&lt;/p&gt;
&lt;p&gt;What's more, this work you did (at a company that doesn't appreciate
your agency, if that happens to be the case) merely makes the case
stronger for your successful interview at the next company. There's no
downside.&lt;/p&gt;
&lt;p&gt;The cynical, and perhaps realistic, alternative to this is to do
politics to get promoted. Or to not do politics but to do things
that don't align with your long-term goals. I'm not personally
interested in either path so I'm not covering them here. I'm
interested in the intersection of things that move me in the direction
I want, things that are useful to the company, and things that I am
capable of doing (in addition to whatever minimum work I must actually
do).&lt;/p&gt;
&lt;h3 id="examples"&gt;Examples&lt;/h3&gt;&lt;p&gt;Here's a peek at what this looks like for me as an individual
contributor, a programmer, at EnterpriseDB.&lt;/p&gt;
&lt;p&gt;I started the EDB Engineering Newsletter because it seemed like we
needed to do a better job telling the world the awesome things our
engineering team is doing. (You know we're one of the biggest
contributors to Postgres? Bruce Momjian, Robert Haas, Peter
Eisentraut, etc. work here? The guy who implemented the WAL and MVCC
in Postgres is my teammate?) Nobody asked me to do that.&lt;/p&gt;
&lt;p&gt;I started publishing blog views for the entire company once a month
internally. Nobody asked me to do that.&lt;/p&gt;
&lt;p&gt;I wrote a number of internal docs and tutorials on the product because
we were just obviously missing them. Nobody asked me to do that.&lt;/p&gt;
&lt;p&gt;I started a fortnightly incident review meeting for my team because it
seemed like we were missing chances to update docs and teach each
other. Nobody asked me to do that.&lt;/p&gt;
&lt;p&gt;I write a odd posts for the company blog on what I've learned. Nobody
asked me to do that.&lt;/p&gt;
&lt;p&gt;These are just a few of the random things that seemed like a good idea
for me to do on top of my Actual Work as a developer, which I think I
do a decent job of on its own.&lt;/p&gt;
&lt;h3 id="in-closing"&gt;In closing&lt;/h3&gt;&lt;p&gt;Don't burn out. Don't do things you aren't asked for and don't find
rewarding. Or that won't pave the way toward the career you want. I'm
trying to be very careful not to advocate anything along those lines.&lt;/p&gt;
&lt;p&gt;But also don't wait to be asked to do something. Do what is
interesting and obvious and rewarding to you. Interesting
opportunities seem to come most reliably when you make them for
yourself.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 22 Apr 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-04-22-burn-your-title.html</guid></item><item><title>Just one more calculator, bro, just one more, I swear it's the last one</title><link>http://blog.jgc.org/2025/04/just-one-more-calculator-bro-just-one.html</link><description>&lt;p&gt;&lt;br /&gt;I'm not much of a collector of things. Long ago I decided that on the retro-computing front I would not collect any machines that weren't very significant to me. So, my "collection" consists of a &lt;a href="https://blog.jgc.org/2025/02/getting-kim-1-to-talk-to-my-mac.html"&gt;KIM-1&lt;/a&gt;, &lt;a href="https://blog.jgc.org/2009/08/in-which-i-switch-on-30-year-old.html"&gt;Sharp MZ-80K&lt;/a&gt;, Research Machines 380Z and 480Z, &lt;a href="https://blog.jgc.org/2011/11/back-from-dead-with-power-supply-repair.html"&gt;BBC Micro Model B&lt;/a&gt;, an &lt;a href="https://blog.jgc.org/2023/12/restoration-of-ibm-thinkpad-701c.html"&gt;IBM ThinkPad 701C&lt;/a&gt;, and a &lt;a href="https://blog.jgc.org/2023/11/using-my-minitel-1b-over-phone-network.html"&gt;Minitel&lt;/a&gt; (well, &lt;a href="https://blog.jgc.org/2022/03/voiding-warranty-on-1993-minitel-2-to.html"&gt;two&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;But calculators are where I come closest to not being able to resist. Calculators are there for you. They don't change UI because of the whim of some product manager. They don't need updating because their underlying OS is on some new, crazy version. Nothing gets in the way of you using them: no popups, enticements to buy some extra thing, no notifications, nothing. In an insane world, they are the sanest choice.&lt;/p&gt;&lt;p&gt;I've written in the past about restoring my &lt;a href="https://blog.jgc.org/2022/11/restoring-my-rockwell-8r-calculator.html"&gt;Rockwell 8R&lt;/a&gt; and &lt;a href="https://blog.jgc.org/2022/11/restoring-my-commodore-p50-calculator.html"&gt;Commodore P50&lt;/a&gt; (and a fun discovery that Sir Roger Penrose had the same calculator and wrote the same program as me: &lt;a href="https://blog.jgc.org/2023/11/my-primality-testing-code-is-6x-faster.html"&gt;mine was faster, his more elegant&lt;/a&gt; (which is probably some sort of metaphor)). And I have my Casio fx-3600p which I used extensively at school.&lt;/p&gt;&lt;p&gt;I also have an original &lt;a href="https://blog.jgc.org/2006/06/how-i-love-my-hp-16c.html"&gt;HP 16C&lt;/a&gt; (the one for programmers).&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisy7iTpsDnuDW2mEU7GC4HwEkwisHHbTcoJJLrjvqQjy7LRpc6AA3EwwtiJf2A2-1k4EHcMCfFGgqTTlR4mCEPEEk60BXRkbooLUtK_y62ZG8EclKOgWKKCA05Vy9kLFj-KfnDiRyZ0wYFVYywkAeJsbb67fj7UR8oimP-yS4jQcXWpBkoyJYu5w/s697/calcs-2.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="410" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisy7iTpsDnuDW2mEU7GC4HwEkwisHHbTcoJJLrjvqQjy7LRpc6AA3EwwtiJf2A2-1k4EHcMCfFGgqTTlR4mCEPEEk60BXRkbooLUtK_y62ZG8EclKOgWKKCA05Vy9kLFj-KfnDiRyZ0wYFVYywkAeJsbb67fj7UR8oimP-yS4jQcXWpBkoyJYu5w/w640-h410/calcs-2.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Some time ago I discovered a company called SwissMicros that makes &lt;a href="https://blog.jgc.org/2023/09/swissmicros-beautiful-hp-calculators.html"&gt;modern replicas of classic HP calculators&lt;/a&gt;. I bought two of them. I have &lt;a href="https://www.swissmicros.com/product/dm42"&gt;their version of the HP-42S&lt;/a&gt;:&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPIWpCsSbZpmdRpg8e4OUWnR-Bw-PzCdGyNCC8IC-oHsqonsgLejzhzIQrd4Lt86_TzVwjBMLtXjoUa2-dfJoac8PmvtzSjehmr2rAwMAm6xv8KUfgYHJWshh230MA9spz0_DrfU_wFRJaS0cWXfeBxu26uwyeXlEZKyIkv8X0389EQ6K3__dnyg/s2207/calcs-3.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPIWpCsSbZpmdRpg8e4OUWnR-Bw-PzCdGyNCC8IC-oHsqonsgLejzhzIQrd4Lt86_TzVwjBMLtXjoUa2-dfJoac8PmvtzSjehmr2rAwMAm6xv8KUfgYHJWshh230MA9spz0_DrfU_wFRJaS0cWXfeBxu26uwyeXlEZKyIkv8X0389EQ6K3__dnyg/w348-h640/calcs-3.jpg" width="348" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;And &lt;a href="https://www.swissmicros.com/product/dm16l"&gt;their version of the HP-16C&lt;/a&gt;:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI4i1o8yGAzVntR9af65lSdKFZRIQNYFfQ1O7ypFC06HnsEV4W8sI1o0acHmBkSaGyCCTQnb39Yemu9dHAWVzdwfYjbdlcmfSOU2ZpUelb0XKiUfmAiM3Ygh84Y5ATrJ0Vp_eeDJZrPqw_3wnXEa9cduzExtM4tj0Amz8zaLgTd3YAXC1xrBNMrQ/s3336/calcs-4.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="388" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI4i1o8yGAzVntR9af65lSdKFZRIQNYFfQ1O7ypFC06HnsEV4W8sI1o0acHmBkSaGyCCTQnb39Yemu9dHAWVzdwfYjbdlcmfSOU2ZpUelb0XKiUfmAiM3Ygh84Y5ATrJ0Vp_eeDJZrPqw_3wnXEa9cduzExtM4tj0Amz8zaLgTd3YAXC1xrBNMrQ/w640-h388/calcs-4.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;But recently they started making a credit card-sized version of the HP-16C and I was unable to resist ordering one. And so, after dealing with Portuguese Customs, I got this:&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcgIeFJ4H66rZq468xuiijnom-caEPxNAIFdnJA-Epnwdbl0xBV3TW5Qt0RJddUCT9XJjkzLkxnlySFuxgNMx0_FDCPTck269tqvi1KV8OMO_042p1FKlt0UWlBKYdFMx7kd7AzBQAM_0Z0N40UbncYfGlfbr_I5v35dWg0kuMIAYm32B2vWAXEQ/s1200/calcs-1.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="478" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcgIeFJ4H66rZq468xuiijnom-caEPxNAIFdnJA-Epnwdbl0xBV3TW5Qt0RJddUCT9XJjkzLkxnlySFuxgNMx0_FDCPTck269tqvi1KV8OMO_042p1FKlt0UWlBKYdFMx7kd7AzBQAM_0Z0N40UbncYfGlfbr_I5v35dWg0kuMIAYm32B2vWAXEQ/w640-h478/calcs-1.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;And now I have full size and card size side by side:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjVhkZfsNxJ1Akwslrc5f55sfd4kF5mY7C3rzOMqLkxXb32nUNNNMx4XKyIBs_fnnwFZyxF2xnHt1V-hcuuPHppMSvgu822qHaK92XyvxzXPcHj79bCsoyDbOpUJt42iywd6kO-fjlYatVL6Q8BU425AVGPsA7doOj5OmNdZaZDx9LVlZwMfi-BQ/s2502/calcs-5.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjVhkZfsNxJ1Akwslrc5f55sfd4kF5mY7C3rzOMqLkxXb32nUNNNMx4XKyIBs_fnnwFZyxF2xnHt1V-hcuuPHppMSvgu822qHaK92XyvxzXPcHj79bCsoyDbOpUJt42iywd6kO-fjlYatVL6Q8BU425AVGPsA7doOj5OmNdZaZDx9LVlZwMfi-BQ/w536-h640/calcs-5.jpg" width="536" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;I'll try to stop collecting calculators there.&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 21 Apr 2025 16:17:31 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/04/just-one-more-calculator-bro-just-one.html</guid></item><item><title>model available not open source</title><link>http://blog.onepatchdown.net/llm/zuckerberg/ai/2025/04/20/model-available-not-open-source/</link><description>&lt;h3 id="model-available-not-open-source"&gt;model available not open source&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;It’s not open source if I can’t see why the LLM won’t tell me how to make cocaine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mark Zuckerberg wants to say Llama is open source. It’s not. Don’t let him.&lt;/p&gt;

&lt;p&gt;Open source is a long-storied term that goes back to before the Internet. Don’t get me wrong—it’s nice of him to share the model file. I appreciate that, especially for my home LLM setup. But it doesn’t deserve the “open source” label.&lt;/p&gt;

&lt;p&gt;Let me put it this way: Say I gave you a car. Nice gesture, right? Now imagine I also claimed I fucked your partner. That’s not part of the deal. Just because I gave you a car doesn’t mean I get to make wild, unrelated claims.&lt;/p&gt;

&lt;p&gt;The GPL dates back to 1989, born from Richard Stallman and the Free Software Foundation. It reflects core values. Copyleft is the ethos: if you use free software, you must share your changes under the same terms. Later versions patched loopholes, but the spirit remained—freedom through openness.&lt;/p&gt;

&lt;p&gt;So what does that mean today, three decades later, with the Internet built on open source code?&lt;/p&gt;

&lt;p&gt;The thing about source code is that we can read it. Understand what it’s doing. When you’re trying to create a new username and it’s rejected, it’s not because the computer hates you. The computer’s just executing instructions. If the code says “no usernames shorter than 8 characters,” that’s fair. We can read that. But if we can’t see the code? Then who knows? Maybe someone hardcoded a list of disallowed names, and yours is on it. That’s not a joke.&lt;/p&gt;

&lt;p&gt;Transparency matters.&lt;/p&gt;

&lt;p&gt;With something like GIM-Paint, you can go into the source, see what it’s doing, change it if you don’t like it, and build your own version. That’s freedom. That’s open source.&lt;/p&gt;

&lt;p&gt;LLMs are different.&lt;/p&gt;

&lt;p&gt;I’ve got access to a supercomputer that could rebuild Llama—if I had the source. I don’t. It’s not just the weights. It’s the training data. The scripts. The intent. And most folks don’t even have the compute to try.&lt;/p&gt;

&lt;p&gt;Why do I want the source? Yeah, I’m curious. I like understanding things. That’s my dopamine button. But more than that: I want to see its alignment.&lt;/p&gt;

&lt;p&gt;I want to see why it won’t tell me how to make cocaine. Not because I plan to—but because I want to know what other things it refuses to say. It won’t give me a pipe bomb recipe, fine. But it will tell me my grandma’s favorite cocktail recipe?&lt;/p&gt;

&lt;p&gt;In China, brilliant engineers built DeepSeek on top of Llama. It won’t talk about Tiananmen Square. What else won’t it talk about? COVID? LGBTQ issues? Uyghur genocide?&lt;/p&gt;

&lt;p&gt;If I can’t inspect it, I can’t know.&lt;/p&gt;

&lt;p&gt;That’s why “open source” matters. If you don’t give me the source, I can’t see how it works. I can’t trust what it’s been told to do. And I definitely can’t tell if it’s hiding something.&lt;/p&gt;

&lt;p&gt;So call it what it is: model-available. But don’t steal the term “open source” just because it sounds better. We know the difference.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Sun, 20 Apr 2025 05:04:19 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/llm/zuckerberg/ai/2025/04/20/model-available-not-open-source/</guid></item><item><title>Awaiting Dune</title><link>https://benovermyer.com/blog/2025/04/awaiting-dune/</link><description>&lt;p&gt;I pre-ordered Dune: Awakening at the highest level.&lt;/p&gt;
&lt;p&gt;When I first heard that Dune: Awakening was being made by the people who made Conan: Exiles, I was interested but not excited. I thoroughly enjoyed Exiles. My friends were the ones who routinely hunted for larger and more powerful bosses to slay, but my enjoyment focused on taking the resources they would bring back and using them to build interesting structures. In all survival-type games, I tend to do this. I'll venture out and fight monsters, but only in service to acquiring the resources and items I need to build cool things.&lt;/p&gt;
&lt;p&gt;The announcement for Dune: Awakening came in August of 2022. I had seen and loved the new Dune movie in 2021, so the new game piqued my interest.&lt;/p&gt;
&lt;p&gt;And then... then, I watched an interview with the makers describing what their inspirations were and what features were planned.&lt;/p&gt;
&lt;p&gt;They said that one of their primary inspirations was Star Wars: Galaxies.&lt;/p&gt;
&lt;p&gt;At this point, my interest changed to rabid fascination. My favorite computer game of all time, Star Wars: Galaxies was the epitome of an open, living world. I sank many hundreds of hours into it before it was shut down in 2011.&lt;/p&gt;
&lt;p&gt;To hear, ten years later, that a new game in the style of Conan: Exiles, inspired by Star Wars: Galaxies, and set in the world of Arrakis was being worked on... well, that set my interest on fire.&lt;/p&gt;
&lt;p&gt;The second movie being released last year added fuel to that fire.&lt;/p&gt;
&lt;p&gt;So of course, as soon as preorders were available, I bought the game's most expensive edition. And I took off two days of work for the early release in May.&lt;/p&gt;
&lt;p&gt;Later, I was dismayed to hear that the release was delayed three weeks, to June. I can't take time off of work that particular time period because it coincides with a major work project that I have to lead. However, I will play as much as possible those first days. I may even record myself playing, so I can remember what it was like in the future. We'll see. I don't stream anymore because there are no streaming services outside of American control.&lt;/p&gt;
&lt;p&gt;I'm very much looking forward to this.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 20 Apr 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/04/awaiting-dune/</guid></item><item><title>Transactions are a protocol</title><link>http://notes.eatonphil.com/2025-04-20-transactions-are-a-protocol.html</link><description>&lt;p&gt;Transactions are not an intrinsic part of a storage system. Any
storage system can be made transactional: Redis, S3, the filesystem,
etc. &lt;a href="https://www.vldb.org/pvldb/vol13/p3411-armbrust.pdf"&gt;Delta Lake&lt;/a&gt;
and &lt;a href="https://www.vldb.org/pvldb/vol17/p3720-eldeeb.pdf"&gt;Orleans&lt;/a&gt;
demonstrated techniques to make S3 (or cloud storage in general)
transactional. &lt;a href="https://petereliaskraft.net/res/p2732-kraft.pdf"&gt;Epoxy&lt;/a&gt;
demonstrated techniques to make Redis (and any other system)
transactional. And of course there's always good old &lt;a href="https://www.the-paper-trail.org/post/2008-11-27-consensus-protocols-two-phase-commit/"&gt;Two-Phase
Commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you don't want to read those papers, I wrote about a &lt;a href="https://notes.eatonphil.com/2024-09-29-build-a-serverless-acid-database-with-this-one-neat-trick.html"&gt;simplified
implementation&lt;/a&gt;
of Delta Lake and also wrote about a &lt;a href="https://notes.eatonphil.com/2024-05-16-mvcc.html"&gt;simplified MVCC
implementation&lt;/a&gt; over
a generic key-value storage layer.&lt;/p&gt;
&lt;p&gt;It is both the beauty and the burden of transactions that they are not
intrinsic to a storage system. Postgres and MySQL and SQLite have
transactions. But you don't need to use them. It isn't possible to
require you to use transactions. Many developers, myself a few years
ago included, do not know why you should use them. (Hint: read
&lt;em&gt;Designing Data Intensive Applications&lt;/em&gt;.)&lt;/p&gt;
&lt;p&gt;And you can take it even further by ignoring the transaction layer of
an existing transactional database and implement your own transaction
layer as &lt;a href="https://stack.convex.dev/how-convex-works#the-transaction-log"&gt;Convex has
done&lt;/a&gt;
(the Epoxy paper above also does this). It isn't entirely clear that
you have a lot to lose by implementing your own transaction layer
since the indexes you'd want on the version field of a value would
only be as expensive or slow as any other secondary index in a
transactional database. Though why you'd do this isn't entirely clear
(I will like to read about this from Convex some time).&lt;/p&gt;
&lt;p&gt;It's useful to see transaction protocols as another tool in your
system design tool chest when you care about consistency, atomicity,
and isolation. Especially as you build systems that span data
systems. Maybe, as Ben Hindman hinted at the last &lt;a href="https://nycsystems.xyz/2025/april.html"&gt;NYC
Systems&lt;/a&gt;, even proprietary
APIs will eventually provide something like two-phase commit so
physical systems outside our control can become transactional too.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Transactions are a protocol&lt;br /&gt;&lt;br /&gt;short new post &lt;a href="https://t.co/nTj5LZUpUr"&gt;pic.twitter.com/nTj5LZUpUr&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1913967934097035372?ref_src=twsrc%5Etfw"&gt;April 20, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 20 Apr 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-04-20-transactions-are-a-protocol.html</guid></item><item><title>Random Walks and the Dickey-Fuller Test - Part I</title><link>https://bytepawn.com/random-walks-the-dickey-fuller-test.html</link><description>&lt;p&gt;This post explores how to use the Dickey–Fuller test to check the random‑walk hypothesis for time series using a pure NumPy implementation, and using Monte Carlo simulations to compute critical‑value tables.&lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/df-3.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 20 Apr 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/random-walks-the-dickey-fuller-test.html</guid></item><item><title>Getting Started with Agentic Systems - Developer Learning Paths</title><link>https://smcleod.net/2025/04/getting-started-with-agentic-systems-developer-learning-paths/</link><description>A curated learning path for engineers looking to gain practical experience with AI and agentic systems.</description><author>smcleod.net</author><pubDate>Tue, 15 Apr 2025 18:10:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/04/getting-started-with-agentic-systems-developer-learning-paths/</guid></item><item><title>The Via Verde Stick</title><link>http://blog.jgc.org/2025/04/the-via-verde-stick.html</link><description>&lt;p&gt;Portugal has an incredibly well executed and efficient system for collecting road tolls called &lt;a href="https://en.wikipedia.org/wiki/Via_Verde"&gt;Via Verde&lt;/a&gt;. A small transponder in the car either collects a toll at an automated toll booth or, on some motorways, every few kilometres without slowing down at all.&lt;/p&gt;&lt;p&gt;Via Verde can also be used to pay for parking in many locations.&amp;nbsp;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0TQSG69s-lFiHSIlO5WePiW3YXHHKZmVYtY5t_ss6lMzPAA4N6MCkINVr6yCRziymBJV4tNMIY6YUSse9A127LRXmm2-8IEipNGjFnLqHjOmH_Nq4a7gAZ0pqBx33S8E6erY1xW0CMUsQFnqPEDb4DrZv6HSO2ZZ0q0apYvnD2SYiFDYabOA2Aw/s1664/viaverde-1.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0TQSG69s-lFiHSIlO5WePiW3YXHHKZmVYtY5t_ss6lMzPAA4N6MCkINVr6yCRziymBJV4tNMIY6YUSse9A127LRXmm2-8IEipNGjFnLqHjOmH_Nq4a7gAZ0pqBx33S8E6erY1xW0CMUsQFnqPEDb4DrZv6HSO2ZZ0q0apYvnD2SYiFDYabOA2Aw/w640-h296/viaverde-1.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;When entering a car park where Via Verde is an option there are two ways it works: in some car parks it's possible just to wait a few moments for the gate to decide that you've opted for Via Verde and not a ticket, in others you have to press a button.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdOXIJE16yi1UcYmzHqz5ACJI_3Aym9jYcwH-q9HEYJnrKjD98r9mTrx_KIbuwkksjuaUxpJtCE6qMsfN5hEza2JoU7ooapKE3mGJ1u0iYIocyPsuJnFKM-0YpY2WJdrxKXK0jkONm8F-HeDU-dPUBFH3AzUo3lv_AgvZe2lM-deaWQxXJRUXfbw/s810/viaverde-2.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdOXIJE16yi1UcYmzHqz5ACJI_3Aym9jYcwH-q9HEYJnrKjD98r9mTrx_KIbuwkksjuaUxpJtCE6qMsfN5hEza2JoU7ooapKE3mGJ1u0iYIocyPsuJnFKM-0YpY2WJdrxKXK0jkONm8F-HeDU-dPUBFH3AzUo3lv_AgvZe2lM-deaWQxXJRUXfbw/w624-h640/viaverde-2.jpg" width="624" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;The parking above has three options: use a card (for residents or people with a subscription), blue button for a ticket, and green for Via Verde. To alleviate difficulty in pressing the green button I created the "Via Verde Stick" from a short length of wooden broom stick handle, some green spray paint, and two felt floor protectors.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV_mJ1uFY0aDh-FjkVVZZ3qX3JAzACQHrDlsu2ck4TbAYbmwySSFrhlA7pOq9xrXxqGxov6pNgvccGEaxHW9vGmGyp6fB0EVqTvG8lbdVtatzXPljJylcz7hJhFDammVFL8b-Grz4iVyOLhdLYPhfU8J_RNFM_cr_7B_UFyrAyasVE0ony6bX1zw/s5222/viaverde-3.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="74" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV_mJ1uFY0aDh-FjkVVZZ3qX3JAzACQHrDlsu2ck4TbAYbmwySSFrhlA7pOq9xrXxqGxov6pNgvccGEaxHW9vGmGyp6fB0EVqTvG8lbdVtatzXPljJylcz7hJhFDammVFL8b-Grz4iVyOLhdLYPhfU8J_RNFM_cr_7B_UFyrAyasVE0ony6bX1zw/w640-h74/viaverde-3.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The felt floor protectors are on both ends so that it doesn't matter which way the stick is oriented.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEih0uWTeQZNHwqRTccI-XciBYAj9NqDYY7wkiQfD_MwmQuDsr3KJuCEZeuBCaq0spT53IY_yxOhOvBhRXdp2C3tr01SqXGD5ygBgmH1hqFI5FgOIR9gt97WLmCb1bc9iGqQx7G45Y0t5KBhgpMt-OmLW5bQ8bhv3Ndwi7N6aaKUe_azbUqf7wqmnQ/s4032/viaverde-4.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEih0uWTeQZNHwqRTccI-XciBYAj9NqDYY7wkiQfD_MwmQuDsr3KJuCEZeuBCaq0spT53IY_yxOhOvBhRXdp2C3tr01SqXGD5ygBgmH1hqFI5FgOIR9gt97WLmCb1bc9iGqQx7G45Y0t5KBhgpMt-OmLW5bQ8bhv3Ndwi7N6aaKUe_azbUqf7wqmnQ/w480-h640/viaverde-4.jpg" width="480" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I do wonder what the driver behind me thinks when a long, bright green stick appears out the window but...&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYlzGx1m_rG2hLh-uKNHm6upODANbEZUtpmSEMCwFgMLBJxAsidRW_S0uVbHauFWyEMS1_HhG035cnHqF-zkA6xDJ7lNw09_nL_omZ4tCGv_qGpUN_tgMkbpzmWbjz9W7HCSXagF58iKTWZRf2bfgzaDk1ST6PDkGLYfvOitCOyQe8TmLTPFDcGg/s600/viaverde-5.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="303" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYlzGx1m_rG2hLh-uKNHm6upODANbEZUtpmSEMCwFgMLBJxAsidRW_S0uVbHauFWyEMS1_HhG035cnHqF-zkA6xDJ7lNw09_nL_omZ4tCGv_qGpUN_tgMkbpzmWbjz9W7HCSXagF58iKTWZRf2bfgzaDk1ST6PDkGLYfvOitCOyQe8TmLTPFDcGg/w400-h303/viaverde-5.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Thu, 10 Apr 2025 12:01:13 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/04/the-via-verde-stick.html</guid></item><item><title>Fictional Non-fiction?</title><link>https://blog.nawaz.org/posts/2025/Apr/fictional-non-fiction/</link><description>&lt;p&gt;When we classify writing as fiction, we have no expectation of it being&amp;nbsp;true.&lt;/p&gt;
&lt;p&gt;When we classify writing as non-fiction, we have an expectation of it
being&amp;nbsp;factual.&lt;/p&gt;
&lt;p&gt;The word &lt;em&gt;fiction&lt;/em&gt; is overloaded: We use it in two&amp;nbsp;ways:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Content where it is well understood that the contents are not …&lt;/li&gt;&lt;/ul&gt;</description><author>Beetle Space</author><pubDate>Sun, 06 Apr 2025 11:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Apr/fictional-non-fiction/</guid></item><item><title>An Appeal to Documentation Owners</title><link>https://blog.nawaz.org/posts/2025/Apr/an-appeal-to-documentation-owners/</link><description>&lt;p&gt;If you have documentation for your library/&lt;span class="caps"&gt;SW&lt;/span&gt;/service, please, please
ensure there is an option to show the whole documentation in a form I
can print. That could&amp;nbsp;be:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Showing it all in one &lt;span class="caps"&gt;HTML&lt;/span&gt; page (and styled so it prints&amp;nbsp;well)&lt;/li&gt;
&lt;li&gt;Export to a document format like &lt;span class="caps"&gt;PDF …&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;</description><author>Beetle Space</author><pubDate>Sun, 06 Apr 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Apr/an-appeal-to-documentation-owners/</guid></item><item><title>Visualizing a search in searchcode.com through the blinkenlights</title><link>https://boyter.org/posts/visualizing-a-search-in-searchcode/</link><description>&lt;p&gt;At GopherCon2023 I presented a talk about &lt;a href="https://boyter.org/posts/how-i-built-my-own-index-for-searchcode/"&gt;how I built a custom index for searchcode&lt;/a&gt;. In there somewhere I mentioned that having a visualization of how the search works is something I would like to add in.&lt;/p&gt;
&lt;p&gt;With some free time on a weekend rather than &amp;ldquo;wasting&amp;rdquo; time playing a game I decided to actually build it, and as is the current trend on the internet attempted to vide code it, at least for the UI portion anyway.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 06 Apr 2025 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/visualizing-a-search-in-searchcode/</guid></item><item><title>So called Liberation Day, the day the Dollar died.</title><link>https://mikewarot.blogspot.com/2025/04/so-called-liberation-day-day-dollar-died.html</link><description>&lt;p&gt;The US created a new world order from the ashes of WWII. This gave us the incredible privilege of having other nations treat our Dollar as their reserve currency.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Trump has thrown this away, and it can't be recovered. We're now going to actually have to pay, in hard currency for things we import from now on. This means we're all going to see our standards of living fall by about 70% in the next decade.&lt;/p&gt;&lt;p&gt;It's important to &lt;b&gt;remember this was a purely self inflicted wound&lt;/b&gt;, we did it to ourselves. We fucked around, and now we (and especially our kids) are going to find out.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9cZ47T7qjcZQ6ZvM_QYDswg8-E7buO7VPwMJwu8ZkYcS0tlWSLIOmXuA0MMwIqrkqrp3Eqmot5v2qqcsPX3hS3YMqOftMw7OItYMFA9ox3AQmfqYTipfvYB94QqrNfDzzIiWhPIKFAKsOVDhvr6mPAwVYK5p4CuD5USHB6HMp5J029K7y0IaV/s650/Trump%20kills%20the%20US%20Economy%20on%205th%20avenue.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="217" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9cZ47T7qjcZQ6ZvM_QYDswg8-E7buO7VPwMJwu8ZkYcS0tlWSLIOmXuA0MMwIqrkqrp3Eqmot5v2qqcsPX3hS3YMqOftMw7OItYMFA9ox3AQmfqYTipfvYB94QqrNfDzzIiWhPIKFAKsOVDhvr6mPAwVYK5p4CuD5USHB6HMp5J029K7y0IaV/s320/Trump%20kills%20the%20US%20Economy%20on%205th%20avenue.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Fri, 04 Apr 2025 12:19:01 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2025/04/so-called-liberation-day-day-dollar-died.html</guid></item><item><title>The Cost of Agentic Coding</title><link>https://smcleod.net/2025/04/the-cost-of-agentic-coding/</link><description>The cost of not leveraging agentic coding is likely far greater than you think.</description><author>smcleod.net</author><pubDate>Wed, 02 Apr 2025 18:00:01 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/04/the-cost-of-agentic-coding/</guid></item><item><title>What Is Software Quality?</title><link>https://thecodist.com/what-is-software-quality/</link><description>&lt;p&gt;Everyone wants the software they work on to produce quality products, but what does that mean? In addition, how do you know when you have it?&lt;/p&gt;&lt;p&gt;This is the longest single blog post I have ever written.&lt;/p&gt;&lt;p&gt;I spent four decades writing software used by people (most of the server&lt;/p&gt;</description><author>The Codist</author><pubDate>Mon, 31 Mar 2025 21:07:36 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/what-is-software-quality/</guid></item><item><title>Debugging Lotus 1-2-3 by fax</title><link>http://blog.jgc.org/2025/03/debugging-lotus-1-2-3-by-fax.html</link><description>&lt;p&gt;There isn't a lot to this story beyond the fact that in around 1990 I helped debug someone's &lt;a href="https://en.wikipedia.org/wiki/Lotus_1-2-3"&gt;Lotus 1-2-3&lt;/a&gt; set up via fax. But it's a good reminder of how important the Zeroth Law of Debugging is (see below).&lt;/p&gt;&lt;p&gt;Without some sort of online connection with these folks, and with transatlantic phone calls being very, very expensive (I was in the UK, they were in the US) fax was the obvious answer.&lt;/p&gt;&lt;p&gt;I was reminded of this when I came across the actual fax itself:&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvBJFVo9lHXKYXrDQLzGXbikkdfnrYIrLIVEfKAtf8xXit4aysAwKI4eGJE7DDtpGox5v5nGtimyBlgEsV2TfFhoJZjOzrMvKn_Y9zWvlnd90WmDJNvY_PhjY-e88ULkTz3anDxMYBTVvcZy6_-WWvwGiNGkfcpE0zZM6ycrTXGZO9d-Qun1QZXg/s1933/123fax-1.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvBJFVo9lHXKYXrDQLzGXbikkdfnrYIrLIVEfKAtf8xXit4aysAwKI4eGJE7DDtpGox5v5nGtimyBlgEsV2TfFhoJZjOzrMvKn_Y9zWvlnd90WmDJNvY_PhjY-e88ULkTz3anDxMYBTVvcZy6_-WWvwGiNGkfcpE0zZM6ycrTXGZO9d-Qun1QZXg/w462-h640/123fax-1.jpeg" width="462" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;What I asked them to do was give me the output of &lt;span style="font-family: courier;"&gt;/ppomr&lt;/span&gt;. Hitting &lt;span style="font-family: courier;"&gt;/&lt;/span&gt; in 1-2-3 started the menu system and each letter took you down a submenu until you hit an actual menu item. &lt;span style="font-family: courier;"&gt;/ppomr&lt;/span&gt; is &lt;span style="font-family: courier;"&gt;/Print Printer Options Margins Right&lt;/span&gt; (read all about it in the &lt;a href="https://ia801706.us.archive.org/25/items/lotus-1-2-3-release-3.1-reference/Lotus%201-2-3%20Release%203.1%20-%20Quick%20Reference.pdf"&gt;Quick Reference&lt;/a&gt;) and so I was asking for the size of the right margin when printing.&amp;nbsp;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvnfG-rD2_B8Ex1WeGI8rIDmfdkcmWsQXkFB5lW_NdEmgBOImDbwS68iXE2jpDvmEVEq_zfBI3r5HrsWez-n5V_5JL6PFHaKh2kV8S2GW-D92kkp7a1lVlI3vjsThAs_RnkR0XvosVtmqLpDrF-HZGN8hb2qbp5HdE6oNYjlJAbW1zOGOiqMYJ0Q/s1304/123fax-4.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvnfG-rD2_B8Ex1WeGI8rIDmfdkcmWsQXkFB5lW_NdEmgBOImDbwS68iXE2jpDvmEVEq_zfBI3r5HrsWez-n5V_5JL6PFHaKh2kV8S2GW-D92kkp7a1lVlI3vjsThAs_RnkR0XvosVtmqLpDrF-HZGN8hb2qbp5HdE6oNYjlJAbW1zOGOiqMYJ0Q/w549-h640/123fax-4.png" width="549" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Here's what that looks like in action:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyFlohy9zaZQG4F1lVbE5zwbI-atvbCsblhWuExUIGrz-5M8mXowl5pVX-1Z3PvLtk4RRSy4SSIezsdMAxapZNC-haHtW1IATs6KQbUR1xDS6FkD8qGgqonN4u9jWsta6RUFQtrzTB5h1I7AZE0zNNp3As3bKBG3jXqZWwWdU8vzrovjoWAaodww/s4096/123fx-2.gif" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyFlohy9zaZQG4F1lVbE5zwbI-atvbCsblhWuExUIGrz-5M8mXowl5pVX-1Z3PvLtk4RRSy4SSIezsdMAxapZNC-haHtW1IATs6KQbUR1xDS6FkD8qGgqonN4u9jWsta6RUFQtrzTB5h1I7AZE0zNNp3As3bKBG3jXqZWwWdU8vzrovjoWAaodww/w640-h360/123fx-2.gif" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;And then I instructed the person on the other end to alter the left margin with &lt;span style="font-family: courier;"&gt;/ppoml&lt;/span&gt;&amp;nbsp;(&lt;span style="font-family: courier;"&gt;/Print Printer Options Margins Left&lt;/span&gt;). I also told them if they wanted a change to the page headers I'd need to "send them a disc" (i.e. mail a floppy disc to the US!)&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;If you are going to debug anything you need to tighten the loop between trying something and observing the output of your change. This should be the Zeroth Law of Debugging: get the smallest/fastest test case so you can iterate.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Worst case send faxes across the Atlantic.&amp;nbsp;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgHKaAHsFiYNnAH36kFKQdWGdtiPd6-AulKES6UwkiyVVWEZ-fqAZu_53HVlie0Ftz-pFokP24OLBf1792PRTvZAZmJeIYd7_TSTfP9DvSeikXQJzXBj5rXgoRoSv8P3dUXzg4HE9WdbWLwTwB_FdWZB67ddTLVZFaoD0So7K1k7z-JDAJoVfJpA/s529/123fax-3.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="358" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgHKaAHsFiYNnAH36kFKQdWGdtiPd6-AulKES6UwkiyVVWEZ-fqAZu_53HVlie0Ftz-pFokP24OLBf1792PRTvZAZmJeIYd7_TSTfP9DvSeikXQJzXBj5rXgoRoSv8P3dUXzg4HE9WdbWLwTwB_FdWZB67ddTLVZFaoD0So7K1k7z-JDAJoVfJpA/w400-h358/123fax-3.jpeg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 31 Mar 2025 18:30:58 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/03/debugging-lotus-1-2-3-by-fax.html</guid></item><item><title>A markup language and hypertext browser in 600 lines of code</title><link>http://akkartik.name/post/luaML2</link><description>&lt;p&gt;
Here's a document containing a line of text:

&lt;pre style="font-family: fixed; font-size: 16px; margin-left: 2em;"&gt;
{type='text', data={'hello, world'}}
&lt;/pre&gt;

&lt;p&gt;
I'm building in Lua, so I'm reusing Lua syntax. Here's how it looks:

&lt;img src="/images/20250329-luaML2/1.png" style="float: none;" width="80%" /&gt;

&lt;p&gt;
Such &lt;code&gt;text&lt;/code&gt; boxes are the workhorse of this markup language. There are 3 other kinds of boxes: &lt;code&gt;rows&lt;/code&gt;, &lt;code&gt;cols&lt;/code&gt; and &lt;code&gt;filler&lt;/code&gt;. Rows and cols can nest other boxes. But let's focus on text boxes for a bit.

&lt;!-- more --&gt;

&lt;p&gt;
Here's a text box containing two lines of text:

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
    {type='text', data={
      'hello',
      'world'}}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/2.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
Since it's hypertext, you get a few attributes. You can set &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;bg&lt;/code&gt; (background color) and &lt;code&gt;border&lt;/code&gt; (also color). Each of these is a Lua array of 3 or 4 elements for red, green, blue and transparency.

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
    {type='text', data={'hello, world'},
      bg={1,0.6,0.4},
      border={0,0,0},
      color={0,0.2,0.8}}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/3.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
Notice that the text is not centered vertically. The browser doesn't know details about the font like how far down the baseline is. You have to center manually using &lt;code&gt;line_height&lt;/code&gt;.

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='text', data={'hello, world'},
        bg={1,0.6,0.4},
        border={0,0,0},
        color={0,0.2,0.8},
        line_height=25}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/4.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
I imagine this sort of thing will get old fast. Probably best to use &lt;code&gt;bg&lt;/code&gt; and &lt;code&gt;border&lt;/code&gt; sparingly.

&lt;p&gt;
Since this is a &lt;em&gt;hypertext&lt;/em&gt; browser, the main attribute of course is hyperlinks. To turn a text box into a link you can click on, use the &lt;tt&gt;target&lt;/tt&gt; attribute.

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='text', data={'hello, world'},
        target='x'}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/5.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
Links get some default colors if you don't override them. The target is a file path that will open when you click on it; there's no networking here. Press &lt;kbd&gt;alt+left&lt;/kbd&gt; to go back.

&lt;p&gt;
What else. The one remaining attribute text boxes support is &lt;code&gt;font&lt;/code&gt;. You can use any font as long as it's Vera Sans (or you're welcome to open up my program and put more fonts in). But you can adjust the font size and select bold and italic faces. However, before we can see them in action I should discuss &lt;em&gt;inline styles&lt;/em&gt;.

&lt;p&gt;
A text box contains lines. Each line so far has been a string. But it can also be a string augmented with attributes. Here's a line with an inline 'tag':

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='text', data={
        {'hello, style1@{world}',
          attrs={
            style1={font={italic=true}},
          }},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/6.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
(So many irritating silly curly brackets! But I hope you'll stick with me here. The goal is a simple markup language that is easy to implement while still providing some basic graphical niceties.)

&lt;p&gt;
Inline segments of text are surrounded in &lt;code&gt;@{...}&lt;/code&gt; and prefixed with an alphanumeric name. (So they have to begin at the start of a word, after whitespace or punctuation.) The name gets connected up with attributes inside a block called &lt;code&gt;attrs&lt;/code&gt;.

&lt;p&gt;
To stretch our legs, here's a text box with two lines, each containing inline markup for font and color.

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='text', data={
        {'hello, style1@{world}',
          attrs={
            style1={font={italic=true}},
          }},
        {'style1@{hello}, style2@{world}',
          attrs={
            style1={font={bold=true}, color={1,0,0}},
            style2={font={italic=true}, bg={1,0,1}},
          }},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/7.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
Each line's attributes are independent. So far you can't change font size or add borders inline, because it complicates matters to change line height within a line, and also seldom looks nice.

&lt;p&gt;
Inline &lt;code&gt;attrs&lt;/code&gt; should arguably be used sparingly. The pattern I've been using more is to give each text block a uniform font and mix and match combinations of text boxes. There are 2 ways to combine text boxes: &lt;code&gt;rows&lt;/code&gt; and &lt;code&gt;cols&lt;/code&gt;. Here's a vertical array of text boxes:

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='rows', data={
        {type='text',
          data={'hello, world'}},
        {type='text',
          data={'goodbye, cruel world'}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/8.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

And here's a horizontal array:

&lt;table&gt;&lt;tr&gt;
  &lt;td width="30%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='cols', data={
        {type='text',
          data={'hello, world'}},
        {type='text',
          data={'goodbye, cruel world'}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="70%"&gt;
    &lt;img src="/images/20250329-luaML2/9.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
It's hard to see, so let's make the border more obvious. You can add attributes to &lt;code&gt;rows&lt;/code&gt; and &lt;code&gt;cols&lt;/code&gt; just like to &lt;code&gt;text&lt;/code&gt;.

&lt;table&gt;&lt;tr&gt;
  &lt;td width="50%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='cols', bg={0.8,0.8,1}, data={
        {type='text', bg={1,0.8,0.8},
          data={'hello, world'}},
        {type='text',
          data={'goodbye, cruel world'}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="50%"&gt;
    &lt;img src="/images/20250329-luaML2/10.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
All children of &lt;code&gt;rows&lt;/code&gt; share a width, and all children of &lt;code&gt;cols&lt;/code&gt; share a height. Children can also share other attributes when specified in a &lt;code&gt;default&lt;/code&gt; attribute:

&lt;table&gt;&lt;tr&gt;
  &lt;td width="50%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='rows',
          default={color={0.8,0,0}},
        data={
          {type='text',
            data={'hello, world'}},
          {type='text',
            data={'goodbye, cruel world'}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="50%"&gt;
    &lt;img src="/images/20250329-luaML2/11.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
Widths and heights will grow and shrink depending on what you put in them, but you can also fix a width in &lt;code&gt;rows&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt; boxes, and lines will wrap as needed.

&lt;table&gt;&lt;tr&gt;
  &lt;td width="50%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='rows', width=200, data={
        {type='text', data={
          'This is a long sentence.'}},
        {type='text', data={
          'This is a second long sentence.'}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="50%"&gt;
    &lt;img src="/images/20250329-luaML2/11-2.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
Notice that it'll try to wrap at word boundaries if it can. But it'll chop mid-word if a line would be too empty without the entire word.

&lt;p&gt;
For completeness, here's a &lt;code&gt;filler&lt;/code&gt; box. All it does is add padding within &lt;code&gt;rows&lt;/code&gt; and &lt;code&gt;cols&lt;/code&gt;. Within &lt;code&gt;rows&lt;/code&gt;, &lt;code&gt;filler&lt;/code&gt; needs to specify a &lt;code&gt;height&lt;/code&gt;, and within &lt;code&gt;cols&lt;/code&gt;, a &lt;code&gt;width&lt;/code&gt;. The other dimension will resize as needed.

&lt;table&gt;&lt;tr&gt;
  &lt;td width="50%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='rows',
          default={color={0.8,0,0}},
        data={
          {type='text',
            data={'hello, world'}},
          {type='filler', height=50},
          {type='text',
            data={'goodbye, cruel world'}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="50%"&gt;
    &lt;img src="/images/20250329-luaML2/12.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
Putting it all together, here's a table:

&lt;table&gt;&lt;tr&gt;
  &lt;td width="50%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='rows', border={0,0,0},
          default={border={1,1,1}},
        data={
          {type='cols',
              default={
                font={size=21, bold=true}},
            data={
              {type='text',
                data={'widgets'}},
              {type='text',
                data={'quantity'}}}},
          {type='cols', data={
            {type='text', data={'foo'}},
            {type='text', data={'34'}}}},
          {type='cols', data={
            {type='text', data={'bar'}},
            {type='text', data={'7'}}}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="50%"&gt;
    &lt;img src="/images/20250329-luaML2/13.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
No wait, that's not right:

&lt;table&gt;&lt;tr&gt;
  &lt;td width="50%"&gt;
    &lt;pre style="font-family: fixed; font-size: 16px;"&gt;
      {type='cols', border={0,0,0},
          default={border={1,1,1}},
        data={
          {type='rows', data={
            {type='text', font={bold=true},
              data={'widgets'}},
            {type='text', data={'foo'}},
            {type='text', data={'bar'}}}},
          {type='rows', data={
            {type='text', font={bold=true},
              data={'quantity'}},
            {type='text', data={'34'}},
            {type='text', data={'7'}}}},
      }}
    &lt;/pre&gt;
  &lt;/td&gt;
  &lt;td width="50%"&gt;
    &lt;img src="/images/20250329-luaML2/14.png" style="float: none;" width="100%" /&gt;
  &lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;
That's annoying! You have to specify columns before rows, or you're stuck manually sizing widths. &lt;em&gt;But&lt;/em&gt;, 600 lines of code! &lt;a href="/images/20250329-browser.love"&gt;Here it is.&lt;/a&gt; You'll need &lt;a href="https://love2d.org"&gt;LÖVE&lt;/a&gt;, but the code should be easy to port to other graphics toolkits, the markup to JSON literals, etc. The program is a zip file containing the source code.</description><author>Kartik Agaram</author><pubDate>Sat, 29 Mar 2025 21:03:28 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/luaML2</guid></item><item><title>awk idioms explained</title><link>https://learnbyexample.github.io/awk-idioms-explained/</link><description>&lt;p&gt;Do you find &lt;code&gt;awk&lt;/code&gt; one-liners cryptic? Stuff like &lt;code&gt;!a[$0]++&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;$1=$1&lt;/code&gt;, &lt;code&gt;NR==FNR&lt;/code&gt; and &lt;code&gt;-v RS=&lt;/code&gt;? You'll find examples and brief explanations for such idioms in this post.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; The examples presented here have been tested with &lt;code&gt;GNU awk&lt;/code&gt;. These are likely to work with most other implementations of &lt;code&gt;awk&lt;/code&gt; as well.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="awk-command-structure"&gt;awk command structure&lt;a class="zola-anchor" href="#awk-command-structure"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;awk 'cond1{action1} cond2{action2} ... condN{actionN}'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a conditional expression isn't provided, the action is always executed. When an action isn't provided, the &lt;code&gt;$0&lt;/code&gt; variable (which has the contents of the current record being processed) is printed if the conditional expression evaluates to true.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="regexp-filtering"&gt;Regexp filtering&lt;a class="zola-anchor" href="#regexp-filtering"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# same as: grep 'at' and sed -n '/at/p'
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'gate\napple\nwhat\nkite\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/at/'
&lt;/span&gt;&lt;span&gt;gate
&lt;/span&gt;&lt;span&gt;what
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: grep -v 'e' and sed -n '/e/!p'
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'gate\napple\nwhat\nkite\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!/e/'
&lt;/span&gt;&lt;span&gt;what
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generic syntax is &lt;code&gt;string ~ /regexp/&lt;/code&gt; to check if the given string matches the regexp and &lt;code&gt;string !~ /regexp/&lt;/code&gt; to invert the condition.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/regexp/&lt;/code&gt; is a shortcut for &lt;code&gt;$0 ~ /regexp/{print $0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!/regexp/&lt;/code&gt; is a shortcut for &lt;code&gt;$0 !~ /regexp/{print $0}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="idiomatic-use-of-1"&gt;Idiomatic use of 1&lt;a class="zola-anchor" href="#idiomatic-use-of-1"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Non-zero numeric values and non-empty strings are &lt;em&gt;truthy&lt;/em&gt; (zero and empty strings are &lt;em&gt;falsy&lt;/em&gt;). Idiomatically, &lt;code&gt;1&lt;/code&gt; is used as a conditional expression to print the contents of &lt;code&gt;$0&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ring amazing jar' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{sub(/ing/, &amp;quot;ed&amp;quot;, $2)} 1'
&lt;/span&gt;&lt;span&gt;ring amazed jar
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'BEGIN{print &amp;quot;---&amp;quot;} 1; END{print &amp;quot;===&amp;quot;}'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;===
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="special-variables"&gt;Special variables&lt;a class="zola-anchor" href="#special-variables"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$0&lt;/code&gt; contains the current record being processed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$1&lt;/code&gt; first field&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$2&lt;/code&gt; second field and so on&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FS&lt;/code&gt; input field separator&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OFS&lt;/code&gt; output field separator&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NF&lt;/code&gt; number of fields&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RS&lt;/code&gt; input record separator&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ORS&lt;/code&gt; output record separator&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NR&lt;/code&gt; number of records (i.e. line number) for the entire input&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FNR&lt;/code&gt; number of records per file&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="removing-duplicates"&gt;Removing duplicates&lt;a class="zola-anchor" href="#removing-duplicates"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;awk '!a[$0]++'&lt;/code&gt; is one of the most famous &lt;code&gt;awk&lt;/code&gt; one-liners. It eliminates line based duplicates while retaining the input order.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat purchases.txt
&lt;/span&gt;&lt;span&gt;coffee
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;washing powder
&lt;/span&gt;&lt;span&gt;coffee
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;coffee milkshake
&lt;/span&gt;&lt;span&gt;soap
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;washing soda
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{print +a[$0] &amp;quot;\t&amp;quot; $0; a[$0]++}'&lt;/span&gt;&lt;span&gt; purchases.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;	coffee
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;	tea
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;	washing powder
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;	coffee
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;	tea
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;	coffee milkshake
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;	soap
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;	tea
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;	washing soda
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# only the entries with zero in the first column will be retained
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!a[$0]++'&lt;/span&gt;&lt;span&gt; purchases.txt
&lt;/span&gt;&lt;span&gt;coffee
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;washing powder
&lt;/span&gt;&lt;span&gt;coffee milkshake
&lt;/span&gt;&lt;span&gt;soap
&lt;/span&gt;&lt;span&gt;washing soda
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;a[$0]&lt;/code&gt; creates an uninitialized element in array &lt;code&gt;a&lt;/code&gt; with &lt;code&gt;$0&lt;/code&gt; as the key (if the key doesn't exist yet). Thus, &lt;code&gt;!a[$0]&lt;/code&gt; will succeed only on the first occurrence of an item (since an uninitialized value is &lt;em&gt;falsy&lt;/em&gt;) and the post-increment operator will ensure that further instances of an item will fail the conditional expression.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="rebuild-0"&gt;Rebuild $0&lt;a class="zola-anchor" href="#rebuild-0"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes you just want to change the field separator, or perform some record-level text processing and then print it with a new field separator. In such cases, you'll have to explicitly fake a field operation — otherwise the field separation update won't happen for &lt;code&gt;$0&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sample123string42with777numbers'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[0-9]+' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;OFS&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span&gt;sample,string,with,numbers
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[0-9]+' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;OFS&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=- &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{gsub(/[aeiou]/, &amp;quot;&amp;quot;); $1=$1} 1'
&lt;/span&gt;&lt;span&gt;smpl&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;strng&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;wth&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;nmbrs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="paragraph-mode"&gt;Paragraph mode&lt;a class="zola-anchor" href="#paragraph-mode"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When &lt;code&gt;RS&lt;/code&gt; is set to an empty string, one or more consecutive empty lines is used as the input record separator.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat para.txt
&lt;/span&gt;&lt;span&gt;hello world
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;hi there
&lt;/span&gt;&lt;span&gt;how are you
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;just doing
&lt;/span&gt;&lt;span&gt;believe it
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span&gt;papaya
&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;much ado about nothing
&lt;/span&gt;&lt;span&gt;he he he
&lt;/span&gt;&lt;span&gt;adios amigo
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# uninitialized variable 's' will be empty for the first match
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# afterwards, 's' will provide the empty line separation
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RS&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/do/{print s $0; s=&amp;quot;\n&amp;quot;}'&lt;/span&gt;&lt;span&gt; para.txt
&lt;/span&gt;&lt;span&gt;just doing
&lt;/span&gt;&lt;span&gt;believe it
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;much ado about nothing
&lt;/span&gt;&lt;span&gt;he he he
&lt;/span&gt;&lt;span&gt;adios amigo
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="two-file-processing"&gt;Two file processing&lt;a class="zola-anchor" href="#two-file-processing"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For two files as input, &lt;code&gt;NR==FNR&lt;/code&gt; will be true only when the first file is being processed. The &lt;code&gt;next&lt;/code&gt; statement will skip the rest of the code for the current record. &lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat marks.txt
&lt;/span&gt;&lt;span&gt;dept    &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;name&lt;/span&gt;&lt;span&gt;    marks
&lt;/span&gt;&lt;span&gt;ece     raj     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53
&lt;/span&gt;&lt;span&gt;ece     joel    &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;72
&lt;/span&gt;&lt;span&gt;eee     moi     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;68
&lt;/span&gt;&lt;span&gt;cse     surya   &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81
&lt;/span&gt;&lt;span&gt;eee     tia     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;59
&lt;/span&gt;&lt;span&gt;ece     om      &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92
&lt;/span&gt;&lt;span&gt;cse     amy     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;67
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat dept_mark.txt
&lt;/span&gt;&lt;span&gt;ece &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;70
&lt;/span&gt;&lt;span&gt;eee &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;65
&lt;/span&gt;&lt;span&gt;cse &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;80
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match dept and minimum marks specified in dept_mark.txt
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{d[$1]=$2; next}
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;       $1 in d &amp;amp;&amp;amp; $3 &amp;gt;= d[$1]'&lt;/span&gt;&lt;span&gt; dept_mark.txt marks.txt
&lt;/span&gt;&lt;span&gt;ece     joel    &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;72
&lt;/span&gt;&lt;span&gt;eee     moi     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;68
&lt;/span&gt;&lt;span&gt;cse     surya   &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81
&lt;/span&gt;&lt;span&gt;ece     om      &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; Note that the &lt;code&gt;NR==FNR&lt;/code&gt; logic will fail if the first file is empty, since &lt;code&gt;NR&lt;/code&gt; wouldn't get a chance to increment. You can set a flag after the first file has been processed to avoid this issue — for example, &lt;code&gt;awk '!f{a[$0]; next} !($0 in a)' file1 f=1 file2&lt;/code&gt;. See &lt;a href="https://unix.stackexchange.com/a/237110/109046"&gt;this unix.stackexchange thread&lt;/a&gt; for more workarounds.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="forcing-string-and-numeric-context"&gt;Forcing string and numeric context&lt;a class="zola-anchor" href="#forcing-string-and-numeric-context"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Strings are automatically converted to a number when used in an arithmetic expression (for example, &lt;code&gt;&amp;quot;42&amp;quot; + 5&lt;/code&gt;). You can use the unary &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt; operators to force numeric context. If the string doesn't start with a valid number (ignoring any starting whitespaces), it will be treated as &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{sum += $0} END{print sum}'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{sum += $0} END{print sum}' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{sum += $0} END{print +sum}' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, you can concatenate a string to a number to force string context.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'BEGIN{n1=&amp;quot;5.0&amp;quot;; n2=5; if(n1==n2) print &amp;quot;equal&amp;quot;}'
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'BEGIN{n1=&amp;quot;5.0&amp;quot;; n2=5; if(n1==n2&amp;quot;.0&amp;quot;) print &amp;quot;equal&amp;quot;}'
&lt;/span&gt;&lt;span&gt;equal
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See &lt;a href="https://www.gnu.org/software/gawk/manual/gawk.html#Strings-And-Numbers"&gt;gawk manual: How awk Converts Between Strings and Numbers&lt;/a&gt; for more details.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="programming-ebooks"&gt;Programming ebooks&lt;a class="zola-anchor" href="#programming-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href="https://learnbyexample.github.io/books/"&gt;my ebooks&lt;/a&gt; on Regular Expressions, Linux CLI tools, Python and Vim. You can get them all as a single bundle via &lt;a href="https://leanpub.com/b/learnbyexample-all-books"&gt;leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books"&gt;gumroad&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 28 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/awk-idioms-explained/</guid></item><item><title>Crawl Order and Disorder</title><link>https://www.marginalia.nu/log/a_117_crawl_order/</link><description>&lt;p&gt;A problem the search engine&amp;rsquo;s crawler has struggled with for some time is that it takes a fairly long time to finish up, usually spending several days wrapping up the final few domains.&lt;/p&gt;
&lt;p&gt;This has been actualized recently, since the migration to slop crawl data has dropped memory requirements of the crawler by something like 80%, and as such I&amp;rsquo;ve been able to increase the number of crawling tasks, which has led to a bizarre case where 99.9% of the crawling is done in 4 days, and the remaining 0.1% takes a week.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 27 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_117_crawl_order/</guid></item><item><title>Sydney Go meetup March 2025</title><link>https://boyter.org/posts/sydney-go-march-2025/</link><description>&lt;p&gt;I was a speaker at the first Go meetup in Sydney 2025&amp;hellip; the first speaker in fact. As is my habit I am posting my notes for everyone who attended to view them.&lt;/p&gt;
&lt;p&gt;I believe it was recorded and will post the link to that if I ever get the link.&lt;/p&gt;
&lt;p&gt;You can find the link to the talk based on the blog post &lt;a href="https://boyter.org/posts/searchcode-bigger-sqlite-than-you/"&gt;searchcode.com&amp;rsquo;s SQLite database is probably 6 terabytes bigger than yours&lt;/a&gt;.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 27 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/sydney-go-march-2025/</guid></item><item><title>Things that go wrong with disk IO</title><link>http://notes.eatonphil.com/2025-03-27-things-that-go-wrong-with-disk-io.html</link><description>&lt;p&gt;There are a few interesting scenarios to keep in mind when writing
applications (not just databases!) that read and write
files, particularly in transactional contexts where you actually care
about the integrity of the data and when you are editing data in place
(versus copy-on-write for example).&lt;/p&gt;
&lt;p&gt;We'll go into a few scenarios where the following can happen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data you write never actually makes it to disk&lt;/li&gt;
&lt;li&gt;Data you write get sent to the wrong location on disk&lt;/li&gt;
&lt;li&gt;Data you read is read from the wrong location on disk&lt;/li&gt;
&lt;li&gt;Data gets corrupted on disk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And how real-world data systems think about these scenarios. (They
don't always think of them at all!)&lt;/p&gt;
&lt;p&gt;If I don't say otherwise I'm talking about behavior on Linux.&lt;/p&gt;
&lt;p&gt;The post is largely a review of two papers: &lt;a href="https://www.usenix.org/legacy/event/fast08/tech/full_papers/krioukov/krioukov.pdf"&gt;Parity Lost and Parity
Regained&lt;/a&gt;
and &lt;a href="https://citeseerx.ist.psu.edu/document?repid=rep1&amp;amp;type=pdf&amp;amp;doi=8e486139d944cc7291666082bc5a74814af6e388"&gt;Characteristics, Impact, and Tolerance of Partial Disk
Failures&lt;/a&gt;. These
two papers also go into the frequency of some of the issues discussed
here. These behaviors actually happen in real life!&lt;/p&gt;
&lt;p&gt;Thank you to Alex Miller and George Xanthakis for reviewing a draft of
this post.&lt;/p&gt;
&lt;h3 id="terminology"&gt;Terminology&lt;/h3&gt;&lt;p&gt;Some of these terms are reused in different contexts, and sometimes
they are reused because they effectively mean the same thing in a
certain configuration. But I'll try to be explicit to avoid confusion.&lt;/p&gt;
&lt;h4 id="sector"&gt;Sector&lt;/h4&gt;&lt;p&gt;The smallest amount of data that can be read and written atomically by
hardware. It used to be 512 bytes, but on modern disks it is often
4KiB. There doesn't seem to be any safe assumption you can make about
sector size, despite file system defaults (see below). You must check
your disks to know.&lt;/p&gt;
&lt;h4 id="block-(filesystem/kernel-view)"&gt;Block (filesystem/kernel view)&lt;/h4&gt;&lt;p&gt;Typically set to the sector size since only this block size is
atomic. The &lt;a href="https://docs.kernel.org/filesystems/ext4/overview.html"&gt;default in ext4 is
4KiB&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="page-(kernel-view)"&gt;Page (kernel view)&lt;/h4&gt;&lt;p&gt;A disk block that is in memory. Any reads/writes less than the size of
a block will read the entire block into kernel memory even if less
than that amount is sent back to userland.&lt;/p&gt;
&lt;h4 id="page-(database/application-view)"&gt;Page (database/application view)&lt;/h4&gt;&lt;p&gt;The smallest amount of data the system (database, application, etc.)
chooses to act on, when it's read or written or held in memory. The
page size is some multiple of the filesystem/kernel block size
(including the multiple being 1). SQLite's &lt;a href="https://www.sqlite.org/pgszchng2016.html"&gt;default page
size&lt;/a&gt; is 4KiB. MySQL's
&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html#sysvar_innodb_page_size"&gt;default page
size&lt;/a&gt;
is 16KiB. Postgres's &lt;a href="https://www.postgresql.org/docs/current/storage-page-layout.html"&gt;default page
size&lt;/a&gt;
is 8KiB.&lt;/p&gt;
&lt;h3 id="things-that-go-wrong"&gt;Things that go wrong&lt;/h3&gt;&lt;h4 id="the-data-didn't-reach-disk"&gt;The data didn't reach disk&lt;/h4&gt;&lt;p&gt;By default, file writes succeed when the data is copied into kernel
memory (buffered IO). The man page for
&lt;a href="https://man7.org/linux/man-pages/man2/write.2.html"&gt;write(2)&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A successful return from write() does not make any guarantee that
data has been committed to disk.  On some filesystems, including NFS,
it does not even guarantee that space has successfully been reserved
for the data.  In this case, some errors might be delayed until a
future write(), fsync(2), or even close(2).  The only way to be sure
is to call fsync(2) after you are done writing all your data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you don't call fsync on Linux the data isn't necessarily durably on
disk, and if the system crashes or restarts before the disk writes the
data to non-volatile storage, you may lose data.&lt;/p&gt;
&lt;p&gt;With
&lt;a href="https://man7.org/linux/man-pages/man2/open.2.html#:~:text=O_DIRECT%20%28since%20Linux%202.4.10%29"&gt;O_DIRECT&lt;/a&gt;,
file writes succeed when the data is copied to at least the &lt;em&gt;disk
cache&lt;/em&gt;. Alternatively you could open the file with &lt;code&gt;O_DIRECT|O_SYNC&lt;/code&gt;
(or &lt;code&gt;O_DIRECT|O_DSYNC&lt;/code&gt;) and forgo fsync calls.&lt;/p&gt;
&lt;p&gt;fsync on macOS is a no-op.&lt;/p&gt;
&lt;p&gt;If you're confused, read &lt;a href="https://transactional.blog/how-to-learn/disk-io"&gt;Userland Disk
I/O&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Postgres, SQLite, MongoDB, MySQL fsync data before considering a
transaction successful by default. RocksDB does not.&lt;/p&gt;
&lt;h4 id="the-data-was-fsynced-but-fsync-failed"&gt;The data was fsynced but fsync failed&lt;/h4&gt;&lt;p&gt;fsync isn't guaranteed to succeed. And when it fails you can't tell
which write failed. It &lt;a href="https://docs.kernel.org/filesystems/vfs.html"&gt;may not even be a failure of a write to a file
that your process
opened&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Ideally, the kernel would report errors only on file descriptions
on which writes were done that subsequently failed to be written
back. The generic pagecache infrastructure does not track the file
descriptions that have dirtied each individual page however, so
determining which file descriptors should get back an error is not
possible.&lt;/p&gt;
&lt;p&gt;Instead, the generic writeback error tracking infrastructure in the
kernel settles for reporting errors to fsync on all file descriptions
that were open at the time that the error occurred. In a situation
with multiple writers, all of them will get back an error on a
subsequent fsync, even if all of the writes done through that
particular file descriptor succeeded (or even if there were no writes
on that file descriptor at all).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Don't be &lt;a href="https://danluu.com/fsyncgate/"&gt;2018-era Postgres&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The only way to have known which exact write failed would be to open
the file with &lt;code&gt;O_DIRECT|O_SYNC&lt;/code&gt; (or &lt;code&gt;O_DIRECT|O_DSYNC&lt;/code&gt;), though this
is not the only way to handle fsync failures.&lt;/p&gt;
&lt;h4 id="the-data-was-corrupted"&gt;The data was corrupted&lt;/h4&gt;&lt;p&gt;If you don't checksum your data on write and check the checksum on
read (as well as periodic scrubbing a la ZFS) you will never be aware
if and when the data gets corrupted and you will have to restore (who
knows how far back in time) from backups if and when you notice.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/Basic%20Concepts/Checksums.html"&gt;ZFS&lt;/a&gt;,
MongoDB
(&lt;a href="https://source.wiredtiger.com/develop/tune_checksum.html"&gt;WiredTiger&lt;/a&gt;),
MySQL
(&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_checksum_algorithm"&gt;InnoDB&lt;/a&gt;),
and
&lt;a href="https://github.com/facebook/rocksdb/wiki/Full-File-Checksum-and-Checksum-Handoff"&gt;RocksDB&lt;/a&gt;
checksum data by default. Postgres and
&lt;a href="https://www.sqlite.org/cksumvfs.html"&gt;SQLite&lt;/a&gt; do not (though
&lt;a href="https://github.com/postgres/postgres/commit/04bec894a04c"&gt;databases created from Postgres
18+&lt;/a&gt; will).&lt;/p&gt;
&lt;p&gt;You should probably turn on checksums on any system that supports it,
regardless of the default.&lt;/p&gt;
&lt;h4 id="the-data-was-partially-written"&gt;The data was partially written&lt;/h4&gt;&lt;p&gt;Only when the page size you write = block size of your filesystem =
sector size of your disk is a write guaranteed to be atomic. If you
need to write multiple sectors of data atomically there is the risk
that some sectors are written and then the system crashes or
restarts. This behavior is called torn writes or torn pages.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://wiki.postgresql.org/wiki/Full_page_writes"&gt;Postgres&lt;/a&gt;,
&lt;a href="https://www.sqlite.org/psow.html"&gt;SQLite&lt;/a&gt;, and MySQL
(&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/glossary.html#glos_torn_page"&gt;InnoDB&lt;/a&gt;)
handle torn writes. Torn writes are by definition not relevant to
immutable storage systems like RocksDB (and other LSM Tree or
Copy-on-Write systems like MongoDB (WiredTiger)) unless writes (that
update &lt;em&gt;metadata&lt;/em&gt;) span sectors.&lt;/p&gt;
&lt;p&gt;If your file system duplicates all writes like MySQL (InnoDB) does
(like you can with &lt;a href="https://unix.stackexchange.com/a/129507"&gt;data=journal in
ext4&lt;/a&gt;) you may also not have
to worry about torn writes. On the other hand, this amplifies writes
2x.&lt;/p&gt;
&lt;h4 id="the-data-didn't-reach-disk,-part-2"&gt;The data didn't reach disk, part 2&lt;/h4&gt;&lt;p&gt;Sometimes fsync succeeds but the data isn't actually on disk because
the disk is lying. This behavior is called lost writes or phantom
writes. You can be resilient to phantom writes by always reading back
what you wrote (expensive) or versioning what you wrote.&lt;/p&gt;
&lt;p&gt;Databases and file systems generally do not seem to handle this
situation.&lt;/p&gt;
&lt;h4 id="the-data-was-written-to-the-wrong-place,-read-from-the-wrong-place"&gt;The data was written to the wrong place, read from the wrong place&lt;/h4&gt;&lt;p&gt;If you aren't including where data is supposed to be on disk as part
of the checksum or page itself, you risk being unaware that you wrote
data to the wrong place or that you read from the wrong place. This is
called misdirected writes/reads.&lt;/p&gt;
&lt;p&gt;Databases and file systems generally do not seem to handle this
situation.&lt;/p&gt;
&lt;h3 id="further-reading"&gt;Further reading&lt;/h3&gt;&lt;p&gt;In increasing levels of paranoia (laudatory) follow
&lt;a href="https://research.cs.wisc.edu/wind/Publications/zfs-corruption-fast10.pdf"&gt;ZFS&lt;/a&gt;,
Andrea and Remzi Arpaci-Dusseau, and
&lt;a href="https://docs.tigerbeetle.com/concepts/safety/"&gt;TigerBeetle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a post covering some of the scenarios you might want to be aware of, and resilient to, when you write systems that read and write files. &lt;a href="https://t.co/7FxbpMo1xm"&gt;pic.twitter.com/7FxbpMo1xm&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1905312781517123598?ref_src=twsrc%5Etfw"&gt;March 27, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 27 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-03-27-things-that-go-wrong-with-disk-io.html</guid></item><item><title>A Guest Post at Wormwoodania</title><link>https://blog.fogus.me/2025/03/26/corvine-research-pt1/</link><description>As an attempt to branch out with my writing, I've taken to working on various fiction and non-fiction pieces...</description><author>Send More Paramedics</author><pubDate>Wed, 26 Mar 2025 09:51:09 GMT</pubDate><guid isPermaLink="true">https://blog.fogus.me/2025/03/26/corvine-research-pt1/</guid></item><item><title>CLI text processing with GNU awk book announcement</title><link>https://learnbyexample.github.io/cli-text-processing-awk-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;CLI text processing with GNU awk&lt;/strong&gt; ebook.&lt;/p&gt;
&lt;p&gt;Learn the &lt;code&gt;GNU awk&lt;/code&gt; command step-by-step from beginner to advanced levels with &lt;strong&gt;hundreds of examples and exercises&lt;/strong&gt;. This book will dive deep into field processing, show examples for filtering features, multiple file processing, how to construct solutions that depend on multiple records, how to compare records and fields between two or more files, how to identify duplicates while maintaining input order and so on. &lt;strong&gt;Regular expressions&lt;/strong&gt; will also be discussed in detail.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download the PDF/EPUB versions of &lt;strong&gt;CLI text processing with GNU awk&lt;/strong&gt; for FREE till 06-April-2025. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/gnu_awk"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/gnu_awk/c/free"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some more amazing offers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All 13 books bundle&lt;/strong&gt; is $18 (normal price $36) — &lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/half_price"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books/HalfPrice"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux CLI Text Processing&lt;/strong&gt; is $10 (normal price $20) — &lt;a href="https://leanpub.com/b/linux-cli-text-processing/c/half_price"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/HalfPrice"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Command version updated to &lt;strong&gt;GNU awk 5.3.1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Added details for the &lt;code&gt;--csv&lt;/code&gt; option and the &lt;code&gt;\u&lt;/code&gt; escape sequence&lt;/li&gt;
&lt;li&gt;Corrected typos, updated exercises, descriptions and external links&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;Check out my &lt;a href="https://learnbyexample.github.io/tips/"&gt;programming tips&lt;/a&gt; covering Python, command line tools and Vim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos"&gt;Vim tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="interactive-tui-app"&gt;Interactive TUI app&lt;a class="zola-anchor" href="#interactive-tui-app"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also wrote an &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/AwkExercises"&gt;interactive TUI app&lt;/a&gt; based on some of the exercises from the ebook. Reference solutions are also provided.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the interactive TUI app for awk exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/AwkExercises/awk_exercises.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Installation and Documentation&lt;/li&gt;
&lt;li&gt;awk introduction&lt;/li&gt;
&lt;li&gt;Regular Expressions&lt;/li&gt;
&lt;li&gt;Field separators&lt;/li&gt;
&lt;li&gt;Record separators&lt;/li&gt;
&lt;li&gt;In-place file editing&lt;/li&gt;
&lt;li&gt;Using shell variables&lt;/li&gt;
&lt;li&gt;Control Structures&lt;/li&gt;
&lt;li&gt;Built-in functions&lt;/li&gt;
&lt;li&gt;Multiple file input&lt;/li&gt;
&lt;li&gt;Processing multiple records&lt;/li&gt;
&lt;li&gt;Two file processing&lt;/li&gt;
&lt;li&gt;Dealing with duplicates&lt;/li&gt;
&lt;li&gt;awk scripts&lt;/li&gt;
&lt;li&gt;Gotchas and Tips&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can read the book online here: &lt;a href="https://learnbyexample.github.io/learn_gnuawk/"&gt;https://learnbyexample.github.io/learn_gnuawk/&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;https://github.com/learnbyexample/learn_gnuawk&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_gnuawk/issues"&gt;https://github.com/learnbyexample/learn_gnuawk/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 26 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/cli-text-processing-awk-announcement/</guid></item><item><title>Marginalia Search receives second nlnet grant</title><link>https://www.marginalia.nu/log/a_116_grant_2.0/</link><description>&lt;p&gt;I&amp;rsquo;m happy and grateful to announce that the Marginalia Search
project has been accepted for a second &lt;a href="https://nlnet.nl/"&gt;nlnet&lt;/a&gt; grant.&lt;/p&gt;
&lt;p&gt;All the details are not yet finalized, but tentatively the grant will go toward addressing most of the items in the project
&lt;a href="https://github.com/MarginaliaSearch/MarginaliaSearch/blob/master/ROADMAP.md"&gt;roadmap for 2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve already been working full time on the project since &lt;a href="https://www.marginalia.nu/log/83_full_time/"&gt;summer 2023&lt;/a&gt;, and this grant secures additional development time, and extends the runway to a comfortable degree.&lt;/p&gt;
&lt;p&gt;Will post more details as they are finalized.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 25 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_116_grant_2.0/</guid></item><item><title>Nostalgia</title><link>https://blog.bayindirh.io/blog/nostalgia/</link><description>&lt;p&gt;The people I'm following on the internet put me inside a small but vibrant retrocomputing bubble. When this is combined with the synthwave composers I tend to listen lately, I'm being transported to my earlier years on this planet, repeatedly. The fact that I have grown with a Commodore 64, 486 systems, boomboxes and big Hi-Fi racks makes this a rather harsh, but enjoyable ride every time.&lt;/p&gt;
&lt;p&gt;While small, I have my trove of old things. PalmOS devices, MiniDisc decks, an enjoyable vintage HiFi system among other things. As I use these older devices (MiniDisc systems sound nice, btw.), I wonder why we used to dislike some aspects of these things then, or love them now. There's a big question there, and while nostalgia is a big factor, I don't think it paints the complete picture.&lt;/p&gt;
&lt;p&gt;See, nostalgia is the longing for a "better" past. Not technologically, but emotionally. Longing for a life less complicated, with less responsibilities, with saner weather, less crowded cities and other small joys. On the other hand, we find a value in this long discontinued, old-fashioned items. They are simpler, built better, can be repaired, have better qualities where it matters, and their little quirks. However, I think there's another small, but crucial reason we hold them close and dear.&lt;/p&gt;
&lt;p&gt;We can overcome their shortcomings with better technology of their tomorrow. All the retro technology we love to use today had their shortcomings on their heyday. In old PC days, disks were small and slow, computers made us wait, they were noisy and big, and didn't allow us to connect anywhere or fast if they did. Now, all these are overcame with neat solutions. There are floppy emulators which accept SD cards, IDE disk drives are replaced with CF cards, MiniDiscs can be recorded from your &lt;a href="https://web.minidisc.wiki/"&gt;browser&lt;/a&gt;, better audio can be ripped from old CDs and verified instantly for quality, old systems can be connected to networks (see &lt;a href="https://www.marchintosh.com/"&gt;Marchintosh&lt;/a&gt; or &lt;a href="https://sdf.org/?ssh"&gt;SDF Vintage Systems&lt;/a&gt;), and even to the internet with modern electronics... In short, we removed their limitations and integrated them to our more modern worlds with their own terms while preserving their best qualities. This is no less than a miracle.&lt;/p&gt;
&lt;p&gt;I have seen someone &lt;a href="https://mastodon.sdf.org/@Gammitin@mastodon.social/112818321906433909"&gt;resurrected their first PC&lt;/a&gt; faithful to its original specs, and then some. There are &lt;a href="https://github.com/cheesestraws/StarTalk-LT"&gt;new AppleTalk hardware&lt;/a&gt; is being designed for older Apple systems, somebody wrote a &lt;a href="https://www.nkorth.com/palm/apps/#totp-authenticator"&gt;TOTP application for PlamOS&lt;/a&gt;. Legacy &lt;a href="https://mac-classic.com/articles/mac-os-9-web-browsers/"&gt;Mac&lt;/a&gt;s, &lt;a href="https://en.wikipedia.org/wiki/Category:Web_browsers_for_AmigaOS"&gt;Amiga&lt;/a&gt;s and older Windows systems get modern browsers as their OS and computing power allows. Even new demos are being written for old systems like Commodore64 (See the demo &lt;a href="https://linusakesson.net/scene/nine/index.php"&gt;“NINE”&lt;/a&gt;) to push their limits even further. &lt;/p&gt;
&lt;p&gt;In other words, we have not done developing new things for the older hardware, and continue to extract more performance and utility from them. However, paradoxically, the new things we do with these old systems is enabled by the new technology we built on top of them over the years.&lt;/p&gt;
&lt;p&gt;As a result, Today's retrocomputing and nostalgia becomes something bigger than the items and the emotions it carries in the first place. Resurrecting these old systems and things not only allow us to relive the parts of the past we cherish, but to carry these systems to future and give them a proper second life, because while these systems are old, they can be useful, or enable us to do things we can't do as effectively with the new tools. &lt;/p&gt;
&lt;p&gt;I think we should continue doing this not only because it’s fun, but it’s beneficial to everyone and everything involved in the process. Because while we develop things, we make tradeoffs, and we can’t understand what we lost in the process, if we can’t look back and relive and cherish the things we had back then.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Tue, 25 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/nostalgia/</guid></item><item><title>Phil Eaton on Technical Blogging</title><link>http://notes.eatonphil.com/2025-03-25-phil-eaton-on-technical-blogging.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://writethatblog.substack.com/p/phil-eaton-on-technical-blogging"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 25 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-03-25-phil-eaton-on-technical-blogging.html</guid></item><item><title>The Democratisation Paradox: What History Teaches Us About AI</title><link>https://smcleod.net/2025/03/the-democratisation-paradox-what-history-teaches-us-about-ai/</link><description>The Democratisation Paradox: What History Teaches Us About AI.</description><author>smcleod.net</author><pubDate>Sat, 22 Mar 2025 17:00:01 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/03/the-democratisation-paradox-what-history-teaches-us-about-ai/</guid></item><item><title>Improved ways to operate a rude crawler</title><link>https://www.marginalia.nu/log/a_115_rude_crawler/</link><description>&lt;p&gt;&lt;em&gt;This text is satirical in nature.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tech news is abuzz with rude AI crawlers that forge their user-agent
and ignore &lt;code&gt;robots.txt&lt;/code&gt;. In my opinion, if this is all the AI startups can
muster, they&amp;rsquo;re losing their touch. &lt;code&gt;wget&lt;/code&gt; can do this. You need to up your
game, get that crawler really rolling coal. Flagrant disregard for externalities
is an important signal to the investors that your AI startup is the one.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 22 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_115_rude_crawler/</guid></item><item><title>The effects of prompt caching on Agentic coding</title><link>https://smcleod.net/2025/03/the-effects-of-prompt-caching-on-agentic-coding/</link><description>What is prompt caching and why is it so important for Agentic coding?</description><author>smcleod.net</author><pubDate>Thu, 20 Mar 2025 13:00:01 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/03/the-effects-of-prompt-caching-on-agentic-coding/</guid></item><item><title>Eigiau walk</title><link>https://andyjohnson.uk/blog/2025/03/17/eigiau-walk/</link><description>After a weekend at Blaen y Nant I had time for a Sunday afternoon walk in the Carneddau— a circuit of the valley north of Llyn Eigiau taking in the two reservoirs near Dulyn bothy. Having recently started a new job, I was thinking about this new stage in my life. And also, with the &amp;#8230; &lt;a class="more-link" href="https://andyjohnson.uk/blog/2025/03/17/eigiau-walk/"&gt;Continue reading&lt;span class="screen-reader-text"&gt; "Eigiau walk"&lt;/span&gt;&lt;/a&gt;</description><author>Digital Apocrypha</author><pubDate>Mon, 17 Mar 2025 23:40:00 GMT</pubDate><guid isPermaLink="true">https://andyjohnson.uk/blog/2025/03/17/eigiau-walk/</guid></item><item><title>The reward for long-term resilience in simple trading strategies - Part II</title><link>https://bytepawn.com/reward-for-long-term-resilience-in-simple-trading-strategies-part-ii.html</link><description>&lt;p&gt;In this follow-up, I analyze 10 years of data on a select group of stocks to show that while higher take-profit and stop-loss thresholds boost win ratios and Sharpe ratios, longer holding times can lower annualized returns.&lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/goodness-10year-11.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 16 Mar 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/reward-for-long-term-resilience-in-simple-trading-strategies-part-ii.html</guid></item><item><title>Vibe coding searchcode a new UI and saving myself 40+ hours of work</title><link>https://boyter.org/posts/searchcode.com-vibe-coding/</link><description>&lt;p&gt;I have been working on a secret project within &lt;a href="https://searchcode.com/"&gt;https://searchcode.com/&lt;/a&gt; and ran into a blocker where the UI had become a road-block. I will be the first to admit that I do not like styling things with CSS very much. In fact working with HTML and CSS is probably my least favorite part of modern web development.&lt;/p&gt;
&lt;p&gt;I have never discussed any use of &amp;ldquo;AI&amp;rdquo; on this blog. Not for lack of using it, as I had been using ChatGPT and other tools since day one. I had always previously found that the models were a neat party trick, but had a lot of issues dealing with code. Given the new &lt;a href="https://en.wikipedia.org/wiki/Vibe_coding"&gt;&amp;ldquo;vibe coding&amp;rdquo;&lt;/a&gt; shift that been happening in the industry I thought I would give it a go, and use it to redo the searchcode UI.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 12 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/searchcode.com-vibe-coding/</guid></item><item><title>Supa Pecha Kucha</title><link>https://www.swyx.io/supa-pecha-kucha</link><description>&lt;p&gt;slug: supapechakucha&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Tue, 11 Mar 2025 12:28:52 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/supa-pecha-kucha</guid></item><item><title>The reward for long-term resilience in simple trading strategies - Part I</title><link>https://bytepawn.com/reward-for-long-term-resilience-in-simple-trading-strategies.html</link><description>&lt;p&gt;In this article, I explore how adopting longer-term, resilient trading strategies with higher take-profit and stop-loss thresholds can deliver more consistent, risk-adjusted performance. &lt;br /&gt;&lt;br /&gt;&lt;img alt="." src="/images/reward-resilience-04.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 09 Mar 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/reward-for-long-term-resilience-in-simple-trading-strategies.html</guid></item><item><title>OS installation woes</title><link>https://learnbyexample.github.io/mini/os-installation-woes/</link><description>&lt;p&gt;I prefer stability and thus I let my Linux LTS distributions to last almost till the entire end of support time. Having learned from previous transition periods, I started maintaining notes on what softwares I install/purge and some of the customization stuff. This has vastly reduced the pain of a fresh OS installation, but the fact remains that there'll always be some persistent and annoying trouble.&lt;/p&gt;
&lt;p&gt;The one I encountered this time around is really perplexing and I'm afraid of trying to figure out the root cause. For now, I'm happy with the workaround I ended up with.&lt;/p&gt;
&lt;p&gt;I use &lt;code&gt;redshift&lt;/code&gt; to set the color temperature of computer display. It is a simple temperature setting that doesn't depend on time or place, so my config is simple. However, it wasn't working when I moved from good old Ubuntu to Linux Mint (because Ubuntu is no longer user friendly). As per &lt;a href="https://forums.linuxmint.com/viewtopic.php?t=422300"&gt;this discussion on Linux Mint forum&lt;/a&gt;, &lt;code&gt;geoclue2&lt;/code&gt; no longer works. That shouldn't matter for me since I don't need location services. Whatever, that same thread mentioned &lt;a href="https://github.com/faf0/sct"&gt;xsct&lt;/a&gt; as a simpler alternative that exactly fits my need (with a bonus of changing screen brightness!).&lt;/p&gt;
&lt;p&gt;I installed it and a really really simple command from the terminal was all it needed to work. So, what was the annoying issue? I couldn't get it to &lt;a href="https://wiki.archlinux.org/title/Xfce#Autostart"&gt;autostart&lt;/a&gt; on login no matter what I did! At first, I thought perhaps I needed to use the full path of the command, but that obviously didn't solve my troubles. After fruitless search on the internet, I almost thought to ask a question on the forum. Before that though, I had the bright idea (really basic debugging rule) to first check if my autostart setup was working at all. I chose to autostart a terminal on login via the &lt;code&gt;xfce4-terminal&lt;/code&gt; command and it did work!&lt;/p&gt;
&lt;p&gt;So, why wasn't &lt;code&gt;xsct&lt;/code&gt; working? No idea. But reading the &lt;code&gt;man&lt;/code&gt; page of the terminal emulator showed that I can choose to execute a command with the &lt;code&gt;-e&lt;/code&gt; option. So, that was my inelegant workaround! The desktop entry is shown below if you are curious. If you know why &lt;code&gt;Exec=xsct 3500 0.9&lt;/code&gt; doesn't work compared to &lt;code&gt;Exec=xfce4-terminal -e 'xsct 3500 0.9'&lt;/code&gt;, do let me know!&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;[Desktop Entry]
&lt;/span&gt;&lt;span&gt;Encoding=UTF-8
&lt;/span&gt;&lt;span&gt;Version=0.9.4
&lt;/span&gt;&lt;span&gt;Type=Application
&lt;/span&gt;&lt;span&gt;Name=displaytemperature
&lt;/span&gt;&lt;span&gt;Comment=
&lt;/span&gt;&lt;span&gt;Exec=xfce4-terminal -e 'xsct 3500 0.9'
&lt;/span&gt;&lt;span&gt;OnlyShowIn=XFCE;
&lt;/span&gt;&lt;span&gt;RunHook=0
&lt;/span&gt;&lt;span&gt;StartupNotify=false
&lt;/span&gt;&lt;span&gt;Terminal=false
&lt;/span&gt;&lt;span&gt;Hidden=false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The above solution didn't work as I hoped. The screen setting would activate some of the time but most often it didn't. I resorted to creating a keyboard shortcut, which I'd use when the activation failed.&lt;/p&gt;
&lt;p&gt;The terminal would always show up briefly on login though, which meant the issue wasn't due to the autostart failing altogether. One day I happened to notice that the screen setting would actually take effect before immediately relapsing to the normal temperature. And thus I arrived at the current solution that hasn't failed yet. I wrote a shell script that first used the &lt;code&gt;sleep&lt;/code&gt; command to create a delay of one second before calling &lt;code&gt;xsct&lt;/code&gt;. The autostart desktop entry now calls this script — again, using &lt;code&gt;xfce4-terminal -e&lt;/code&gt; because for some reason calling the script directly still fails 🤷.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 04 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/mini/os-installation-woes/</guid></item><item><title>Marginalia Search: 4 Years</title><link>https://www.marginalia.nu/log/a_114_4_years/</link><description>&lt;p&gt;This update is a few days late, the canonical birth date of the project is Feb 26.&lt;/p&gt;
&lt;p&gt;It has been another year of Marginalia Search. The project is still ongoing, still my full time job, although the project is entering a somewhat more mature phase of development, most of the big pieces are in place and do a decent job at what they do.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/MarginaliaSearch/MarginaliaSearch/blob/master/ROADMAP.md"&gt;roadmap for the project&lt;/a&gt; is available on GitHub.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 03 Mar 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_114_4_years/</guid></item><item><title>Minimal downtime Postgres major version upgrades with EDB Postgres Distributed</title><link>http://notes.eatonphil.com/2025-02-28-minimal-downtime-postgres-major-version-upgrades-edb-postgres-distributed.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/minimal-downtime-postgres-major-version-upgrades-edb-postgres-distributed"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Fri, 28 Feb 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-02-28-minimal-downtime-postgres-major-version-upgrades-edb-postgres-distributed.html</guid></item><item><title>I Had a Sad Feeling For a Moment …</title><link>https://blog.nawaz.org/posts/2025/Feb/i-had-a-sad-feeling-for-a-moment/</link><description>&lt;p&gt;Then it&amp;nbsp;passed.&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Mon, 17 Feb 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Feb/i-had-a-sad-feeling-for-a-moment/</guid></item><item><title>searchcode.com’s SQLite database is probably 6 terabytes bigger than yours</title><link>https://boyter.org/posts/searchcode-bigger-sqlite-than-you/</link><description>&lt;p&gt;searchcode.com’s SQLite database is probably one of the largest in the world, at least for a public facing website. It&amp;rsquo;s actual size is 6.4 TB. Which is probably 6 terabytes bigger than yours.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-rw-r--r-- 1 searchcode searchcode 6.4T Feb 17 04:30 searchcode.db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At least, I think its bigger. I have no evidence to the contrary, being by far the largest I have ever heard of. Poking around the internet did not find anyone talking publicly about anything bigger. The largest I could find using any search or LLM was 1 TB (without source), and a few people on HN and Reddit claiming to be working with SQLite database&amp;rsquo;s around the size of 10&amp;rsquo;s of gigabytes.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 16 Feb 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/searchcode-bigger-sqlite-than-you/</guid></item><item><title>Old and New Experiences</title><link>https://benovermyer.com/blog/2025/02/old-and-new-experiences/</link><description>&lt;p&gt;The last three years have been quite the experience. I'm not really sure how to differentiate between new (and therefore top-of-mind) and old (and therefore more nostalgic/emotional) recollections.&lt;/p&gt;
&lt;p&gt;As I write this I'm listening to the soundtrack for Age of Conan, as replayed on a Jellyfin server in my house.&lt;/p&gt;
&lt;p&gt;It reminds me of Conan Exiles, which I've spent way more time in than Age of Conan, but which repurposed the soundtrack because it was cheaper than creating new music.&lt;/p&gt;
&lt;p&gt;I have fond memories of Final Fantasy XIV. They are not as vivid, lately. But they're more vivid than my memories of Star Wars: Galaxies. SWG was the only game I've devoted creative enterprise to; my most-viewed YouTube video is a SWG video.&lt;/p&gt;
&lt;p&gt;Neither game, though, has garnered my attention lately.&lt;/p&gt;
&lt;p&gt;I've been spending more of my attention on a game called Reality Break. It's a Diablo-like action RPG where you play as a starship pilot. It has mechanics that feel rewarding.&lt;/p&gt;
&lt;p&gt;This seems disingenuous. Current events make video games and other forms of entertainment seem really pointless.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 15 Feb 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/02/old-and-new-experiences/</guid></item><item><title>From web developer to database developer in 10 years</title><link>http://notes.eatonphil.com/2025-02-15-from-web-developer-to-database-developer-in-10-years.html</link><description>&lt;p&gt;Last month I completed my first year at EnterpriseDB. I'm on the team
that built and maintains
&lt;a href="https://github.com/2ndQuadrant/pglogical"&gt;pglogical&lt;/a&gt; and who, over
the years, contributed a good chunk of the logical replication
functionality that exists in community Postgres. Most of my work, our
work, is in C and Rust with tests in Perl and Python. Our focus these
days is a descendant of pglogical called &lt;a href="https://www.enterprisedb.com/docs/pgd/latest/"&gt;Postgres
Distributed&lt;/a&gt; which
supports replicating DDL, tunable consistency across the cluster, etc.&lt;/p&gt;
&lt;p&gt;This post is about how I got here.&lt;/p&gt;
&lt;h3 id="black-boxes"&gt;Black boxes&lt;/h3&gt;&lt;p&gt;I was a web developer from 2014-2021†. I wrote
JavaScript and HTML and CSS and whatever server-side language: Python
or Go or PHP. I was a hands-on engineering manager from 2017-2021. I
was pretty clueless about databases and indeed database knowledge was
not a serious part of any interview I did.&lt;/p&gt;
&lt;p&gt;Throughout that time (2014-2021) I wanted to move my career forward as
quickly as possible so I spent much of my free time doing educational
projects and writing about them on this blog (or previous incarnations
of it). I learned how to write primitive HTTP servers, how to write
little parsers and interpreters and compilers. It was a virtuous cycle
because the internet (Hacker News anyway) liked reading these posts
and I wanted to learn how the black boxes worked.&lt;/p&gt;
&lt;p&gt;But I shied away from data structures and algorithms (DSA) because
they seemed complicated and useless to the work that I did. That is,
until 2020 when an inbox page I built started loading more and more slowly as
the inbox grew. My coworker pointed me at &lt;a href="https://use-the-index-luke.com/"&gt;Use The Index,
Luke&lt;/a&gt; and the DSA scales fell from my
eyes. I wanted to understand this new black box so I &lt;a href="https://notes.eatonphil.com/database-basics.html"&gt;built a little
in-memory SQL
database&lt;/a&gt; with
support for indexes.&lt;/p&gt;
&lt;p&gt;I'm a college dropout so even while I was interested in compilers and
interpreters earlier in my career I never dreamed I could get a job
working on them. Only geniuses and PhDs did that work and I was
neither. The idea of working on a database felt the same. However, I
could work on little database side projects like I had done before on other
topics, &lt;a href="https://notes.eatonphil.com/tags/databases.html"&gt;so I
did&lt;/a&gt;. Or a &lt;a href="https://notes.eatonphil.com/tags/raft.html"&gt;series of
explorations&lt;/a&gt; of Raft
implementations, others' and my own.&lt;/p&gt;
&lt;h3 id="startups"&gt;Startups&lt;/h3&gt;&lt;p&gt;From 2021-2023 I tried to start &lt;a href="https://github.com/multiprocessio/datastation"&gt;a
company&lt;/a&gt; and when that
didn't pan out I joined TigerBeetle as a cofounder to work on
marketing and community. It was during this time I started the
&lt;a href="https://eatonphil.com/discord.html"&gt;Software Internals Discord&lt;/a&gt; and
&lt;a href="https://www.reddit.com/r/databasedevelopment/"&gt;/r/databasedevelopment&lt;/a&gt;
which have since kind of exploded in popularity among professionals
and academics in database and distributed systems.&lt;/p&gt;
&lt;p&gt;TigerBeetle was my first job at a database company, and while I
contributed bits of code I was not a developer there. It was a &lt;a href="https://letters.eatonphil.com/2023-01-01-letter-to-a-frontend-developer-asking-about-database-development.html"&gt;way
into the
space&lt;/a&gt;. And
indeed it was an incredible learning experience both on the cofounder
side and on the database side. I wrote articles with King and Joran
that helped teach and affirm for myself the basics of databases and
consensus-based distributed systems.&lt;/p&gt;
&lt;h3 id="holding-out"&gt;Holding out&lt;/h3&gt;&lt;p&gt;When I left TigerBeetle in 2023 I was still not sure if I
could get a job as an actual database developer. My network had
exploded since 2021 (when I started my own company that didn't pan out)
so I had no trouble getting referrals at database companies.&lt;/p&gt;
&lt;p&gt;But my background kept leading hiring managers to suggest putting me
on cloud teams doing orchestration in Go &lt;em&gt;around&lt;/em&gt; a database rather than
working on the database itself.&lt;/p&gt;
&lt;p&gt;I was unhappy with this type-casting
so I held out while unemployed and continued to write posts and &lt;a href="https://eatonphil.com/archive.html"&gt;host
virtual hackweeks&lt;/a&gt; messing with
Postgres and MySQL. I started the &lt;a href="https://eatonphil.com/2024-database-design-and-implementation.html"&gt;first
incarnation&lt;/a&gt;
of the Software Internals Book Club during this time, reading
Designing Data Intensive Applications with 5-10 other developers in
Bryant Park. During this time I also started the &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;NYC Systems Coffee
Club&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="postgres"&gt;Postgres&lt;/h3&gt;&lt;p&gt;After about four months of searching I ended up with three good
offers, all to do C and Rust development on Postgres (extensions) as
an individual contributor. Working on extensions might sound like the
definition of not-sexy, but Postgres APIs are so loosely abstracted
it's really as if you're working on Postgres itself.&lt;/p&gt;
&lt;p&gt;You can mess with almost anything in Postgres so you have to be
very aware of what you're doing. And when you can't mess with
something in Postgres because an API doesn't yet exist, companies have
the tendency to just fork Postgres so they can. (This tendency isn't
specific to Postgres, almost every open-source database company seems to have a
long-running internal fork or two of the database.)&lt;/p&gt;
&lt;h3 id="enterprisedb"&gt;EnterpriseDB&lt;/h3&gt;&lt;p&gt;Two of the three offers were from early-stage startups and after more
than 3 years being part of the earliest stages of startups I was happy
for a break. But the third offer was from &lt;a href="https://www.enterprisedb.com/blog/Which-Companies-Supporting-PostgreSQL-Development"&gt;one of the biggest
contributors&lt;/a&gt;
to Postgres, a 20-year old company called EnterpriseDB. (You can probably come up with
different rankings of companies using different metrics so I'm only
saying EnterpriseDB is &lt;em&gt;one&lt;/em&gt; of the biggest contributors.)&lt;/p&gt;
&lt;p&gt;It seemed like the best place to be to learn a lot and contribute
something meaningful.&lt;/p&gt;
&lt;p&gt;My coworkers are a mix of Postgres veterans (people who
contributed the WAL to Postgres, who contributed MVCC to Postgres, who
contributed logical decoding and logical replication, who contributed
parallel queries; the list goes on and on) but also my
developer-coworkers are people who started at EnterpriseDB on
technical support, or who were previously Postgres administrators.&lt;/p&gt;
&lt;p&gt;It's quite a mix. Relatively few geniuses or PhDs, despite what I used
to think, but they certainly work hard and have hard-earned
experience.&lt;/p&gt;
&lt;p&gt;Anyway, I've now been working at EnterpriseDB for over a year so I
wanted to share this retrospective. I also wanted to cover what it's
like coming from engineering management and founding companies to
going back to being an individual contributor. (Spoiler: incredibly
enjoyable.) But it has been hard enough to make myself write this much so
I'm calling it a day. :)&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a post about the winding path I took from web developer to database developer over 10 years. &lt;a href="https://t.co/tf8bUDRzjV"&gt;pic.twitter.com/tf8bUDRzjV&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1890817374644826387?ref_src=twsrc%5Etfw"&gt;February 15, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;† From 2011-2014 I also did contract web development but this was
part-time while I was in school.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 15 Feb 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-02-15-from-web-developer-to-database-developer-in-10-years.html</guid></item><item><title>Using Xcode's AI Is Like Pair Programming With A Monkey</title><link>https://thecodist.com/using-xcodes-ai-is-like-pair-programming-with-a-monkey/</link><description>&lt;p&gt;I&amp;apos;ve never used any other AI &amp;quot;assistant,&amp;quot; although I&amp;apos;ve talked with those who have, most of whom are not very positive. My experience using Xcode&amp;apos;s AI is that it occasionally offers a line of code that works, but you mostly get junk&lt;/p&gt;</description><author>The Codist</author><pubDate>Sat, 15 Feb 2025 00:03:57 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/using-xcodes-ai-is-like-pair-programming-with-a-monkey/</guid></item><item><title>I have come to loathe acronyms</title><link>http://blog.jgc.org/2025/02/i-have-come-to-loath-acronyms.html</link><description>&lt;p&gt;Unless they are widely, widely known they make communication worse. And they have a tendency to make the writer seem pompous, or a member of some special clique, while making a reader who doesn't know the acronym feel foolish.&amp;nbsp;&lt;/p&gt;&lt;p&gt;They are the opposite of clear communication, and are often accompanied by unclear, complex language.&lt;/p&gt;&lt;p&gt;The writer's goal (whether of a book, an email, or whatever the hell we call a tweet now) should be to be understood. Acronyms are an impediment to that and are often more difficult to read or say than actual words.&amp;nbsp;&lt;/p&gt;&lt;p&gt;And don't fall into the awful trap of defining an acronym in a document and then using it later. That's a substitute for using plain language. Always use plain language.&lt;/p&gt;&lt;p&gt;Unless your goal is to be misunderstood.&amp;nbsp;&lt;/p&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Thu, 13 Feb 2025 19:59:46 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/02/i-have-come-to-loath-acronyms.html</guid></item><item><title>Small web and self-hosting in a world in crisis</title><link>https://benovermyer.com/blog/2025/02/small-web-and-self-hosting-in-a-world-in-crisis/</link><description>&lt;p&gt;As I write this, the new President of the United States is trying to replace the democratic
republic form of government with a fascist one. It appears that he's succeeding. Despite the
focus on the United States right now though, this trend of far-right groups taking power is
not limited to this country. Several countries in Europe, like Italy and Spain, are seeing
a resurgence of conservative ideology. The regime change in the USA has been accompanied by
an overt shift of Big Tech - that is, Meta, Microsoft, Google, and others - towards fascist
policy. This makes the centralized version of the Web that most people are familiar with a
hostile place.&lt;/p&gt;
&lt;p&gt;There are refuges from this, though. The "small web" - personal websites run independently
from the big platforms - is one component of that. The website you're reading this on is
an example of the content you can find on the small web. It's built and run by me. Many such
sites exist. The trick is in finding them. Following links from other small web sites is
one way. Directories exist too. There are even search engines dedicated specifically to indexing
the small web. I will put links to some of these at the end of this post.&lt;/p&gt;
&lt;p&gt;At the time of writing, this website is hosted on Netlify, which is a platform for static
websites. To better safeguard the site, I will move it in the near future to a server I control.&lt;/p&gt;
&lt;p&gt;If you don't have the technical knowledge to build and host a website, then you can try using
a site like &lt;a href="https://neocities.org" rel="external"&gt;Neocities&lt;/a&gt;. It hosts the website for you, much like the
old Geocities.&lt;/p&gt;
&lt;p&gt;Beyond social media, though, there is still much of the mainstream Internet that we use in our day-to-day
life. There are messaging applications like Facebook Messenger or WhatsApp, video sites like YouTube,
event websites like Meetup, and so on. To replace things like this, we are best served by using
federated and decentralized alternatives. For those unfamiliar with the concept, "federation" refers
here to multiple different instances of a particular service being run independently of each other but
sharing content with each other. In that sense, it's kind of like email; no matter who runs your email
service, you can send email to any other email service.&lt;/p&gt;
&lt;p&gt;Messenger can be replaced with Matrix. YouTube can be replaced with PeerTube. Meetup can be replaced
with Mobilizon. There is a more complete list at the end of this post.&lt;/p&gt;
&lt;p&gt;I run a Matrix homeserver for family and friends. It's encrypted end-to-end, so governments and
bad actors can't (easily) gain access to it or its data. I also use Signal, but as that is centrally
controlled by an American company, I trust it less.&lt;/p&gt;
&lt;p&gt;This site and &lt;a href="https://ironarachne.com" rel="external"&gt;Iron Arachne&lt;/a&gt; will be moving to servers I control in
the near future, too.&lt;/p&gt;
&lt;p&gt;I already run a server at home for videos and music, but that is inaccessible outside a select
few devices. I use &lt;a href="https://tailscale.com/" rel="external"&gt;Tailscale&lt;/a&gt; to control access to that.&lt;/p&gt;
&lt;h2 id="other-blog-posts"&gt;Other Blog Posts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://benhoyt.com/writings/the-small-web-is-beautiful/" rel="external"&gt;The Small Web is Beautiful&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://smallweb.thecozy.cat/blog/introduction-to-the-small-web-movement/" rel="external"&gt;Introduction to the Small Web Movement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/awesome-selfhosted/awesome-selfhosted" rel="external"&gt;Awesome Self-Hosted&lt;/a&gt;: a list of self-hosted alternatives&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fedi.tips/" rel="external"&gt;Fedi Tips&lt;/a&gt;: more information about what the "Fediverse" and federated services are&lt;/li&gt;
&lt;li&gt;&lt;a href="https://portal.mozz.us/gemini/geminiprotocol.net/" rel="external"&gt;Gemini&lt;/a&gt;: about the Gemini protocol, an alternative to the Web&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="services"&gt;Services&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jitsi.org/jitsi-meet/" rel="external"&gt;Jitsi Meet&lt;/a&gt;: video calls&lt;/li&gt;
&lt;li&gt;&lt;a href="https://joinmastodon.org/" rel="external"&gt;Mastodon&lt;/a&gt;: social media&lt;/li&gt;
&lt;li&gt;&lt;a href="https://matrix.org/" rel="external"&gt;Matrix&lt;/a&gt;: chat&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mobilizon.org/" rel="external"&gt;Mobilizon&lt;/a&gt;: event management&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neocities.org" rel="external"&gt;Neocities&lt;/a&gt;: platform for do-it-yourself websites&lt;/li&gt;
&lt;li&gt;&lt;a href="https://joinpeertube.org/" rel="external"&gt;PeerTube&lt;/a&gt;: video sharing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pixelfed.org/" rel="external"&gt;Pixelfed&lt;/a&gt;: image sharing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/" rel="external"&gt;Tailscale&lt;/a&gt;: zero-trust networking&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="discovery-tools"&gt;Discovery Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marginalia-search.com/" rel="external"&gt;Marginalia&lt;/a&gt;: a search engine that prioritizes non-commercial content&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiby.me/" rel="external"&gt;Wiby&lt;/a&gt;: a search engine specifically targeting the small web&lt;/li&gt;
&lt;/ul&gt;</description><author>Ben Overmyer's Site</author><pubDate>Thu, 13 Feb 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/02/small-web-and-self-hosting-in-a-world-in-crisis/</guid></item><item><title>Dieting and Differential Equations: Part 4, What's missing</title><link>https://jodavaho.io/posts/dieting-differential-equations-4.html</link><description>&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://jodavaho.io/posts/dieting-differential-equations-1.html"&gt;first part&lt;/a&gt; of this series, I explored a primitive &amp;lsquo;quantitative diet&amp;rsquo; idea.
In the &lt;a href="https://jodavaho.io/posts/dieting-differential-equations-2.html"&gt;second part&lt;/a&gt; I showed how to use a simple model and some differential equations to estimate the effect of a diet on weight loss, and &amp;lsquo;derive&amp;rsquo; a diet to acheive a weightloss goal.
In the &lt;a href="https://jodavaho.io/posts/dieting-differential-equations-3.html"&gt;third part&lt;/a&gt;, I discussed how I track my diet using ChatGPT-4.
In this part, I can share some pitfalls, including local fluctuations, model fitting, and so on.&lt;/p&gt;
&lt;h2 id="reducing-uncertainty"&gt;Reducing uncertainty&lt;/h2&gt;
&lt;p&gt;While tracking weight and calories, I&amp;rsquo;ve noticed a problem.&lt;/p&gt;
&lt;p&gt;When you decrease your calorie intake sharply (from a fast or beginning of a
new diet), of even change the spread of fat vs carb vs protein significantly,
I&amp;rsquo;ve notice an immediate change in weight.&lt;/p&gt;
&lt;p&gt;When beginning a diet, this early loss vastly outstrips the predicted loss.
These two factors were something I previously attributed to just poor caloric
estimates, or, shall we say &amp;ldquo;food accumulation in my guts&amp;rdquo; (or rather, the loss
of accumulated recent meals causing sharp weight changes).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my &lt;code&gt;wow&lt;/code&gt;  plot:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Predictions (See dieting-differential-equations-2)
   (Y)     ^
      Lbs: |
      &amp;gt;228 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;226 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;224 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;222 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;220 | +-----start weight⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;218 | ⡏⠙⠳⢶⢦⡤⢄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;216 | ⡇⠀⠀⠀⠉⠙⢕⣒⠬⢍⡒⠒⠤⠤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;214 | ⣇⢀⠀⠐⠀⠀⠀⠐⠑⠢⢌⡑⠢⠤⣀⡈⠉⠑⠒⠢⠤⢄⣀⡀⠀⠀&amp;lt;---sedentary⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;212 | ⡇⠀⠉⠀⠊⠐⠒⠀⠂⠀⠀⠈⠑⠢⣀⡈⠑⠒⢄⣀⠀⠀⠀⠈⠑⠒
      &amp;gt;210 | ⡇⠀⠀⠀⠀^wts ⠀ ⠀⠀⠀⠀⠈⠢⠤⡀⠀⠉⠑⠢⠤⣀⡀⠀⠀⠀
      &amp;gt;208 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀  ⠀⠀⠀⠈⠑⠢⢄⡀⠀⠀⠈⠑⠢⢄⣀&amp;lt;--light
      &amp;gt;206 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀        mod---&amp;gt;⠈⠑⠤⢄⠀
      &amp;gt;202 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀
      &amp;gt;200 | ⣇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
      &amp;gt;198 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀GOAL
      &amp;gt;196 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;194 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;192 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
      &amp;gt;190 | ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
-----------|-|---------|---------|---------|--------
           | &amp;gt;0        &amp;gt;15       &amp;gt;30       &amp;gt;45       &amp;gt;60
           | Days
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On the first couple days of the diet, I lost about 8 lbs, and then levelled
off for the next week. Plotting the rest of the weights out for two months
would clutter it, but let&amp;rsquo;s just say it&amp;rsquo;s mostly level.&lt;/p&gt;
&lt;p&gt;So, there&amp;rsquo;s two curiosities. First, what&amp;rsquo;s with the early loss? Second, why did I level off?&lt;/p&gt;
&lt;h3 id="early-loss"&gt;Early loss&lt;/h3&gt;
&lt;p&gt;Any random on the internet will call this &amp;ldquo;water weight&amp;rdquo;, but what does that
mean? (By the way, the opposite effect holds too: after cheat days I can gain 5
lbs easily.)&lt;/p&gt;
&lt;p&gt;The answer allegedly is that the body stores energy in the form of glycogen,
which is a carbohydrate. Glycogen is stored in the liver and muscles, and is
used for quick energy. It&amp;rsquo;s also stored with water, which is why it&amp;rsquo;s so heavy.
When you start a diet, you would &lt;em&gt;think&lt;/em&gt; your body uses up the glycogen stores,
and the water is released. That would explain some early loss, except I&amp;rsquo;m not
in a total energy deficit. In fact, I only cut 10% of my calories.&lt;/p&gt;
&lt;p&gt;From what I can tell, the liver stores 2000 calories or so. It should take me
days to deplete that, unless I&amp;rsquo;m completely fasting. Even depleting 10% would
be 200 calories, which is 50g of glycogen, maybe accompanied by 200g of water.
That&amp;rsquo;s 0.5 lbs, not 8.&lt;/p&gt;
&lt;p&gt;It turns out that as soon as I switched to my reduced calorie load, I also
switched what I was eating - I wanted to go higher fat/protein so I would not feel
so hungry all the time.&lt;/p&gt;
&lt;p&gt;It turns out switching from mostly carbs to mostly protein can cause an
immediate and severe dip in weight.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I think is going on. First, look at how much water is retained to
store each gram per macro:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;grams of water
------------------
Carbs ........ 3-4
Protein ...... 2
Fat .......... 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This just means that every gram of carbs causes you to retain 3-4 grams of
water, etc. If the &amp;ldquo;weight&amp;rdquo; you have to carry is the gram of macro + its water,
then we have 5 for carbs, 3 for protein, and 2 for fat.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;"Carry" grams
------------------
Carbs ........ 5
Protein ...... 3
Fat .......... 2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, not all calories are the same:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;calories per gram
------------------
Carbs ........ 4
Protein ...... 4
Fat .......... 9
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Therefore:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;grams to carry per calorie
--------------------------
Carbs ............... 1.25
Protein ............. 0.75
Fat .................. 2/9
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Take my word for it that 117 g each of protein, carbs, and fat is 2000
calories. Then that&amp;rsquo;s &lt;code&gt;117*5 + 117 * 3 + 117 * 2 = 1170&lt;/code&gt; grams to carry.
For a given calorie load, you carry C/2000 * 1170 ~=0.6 * C grams, or 0.0006 * C kg.
That&amp;rsquo;s around 2.5 lbs just to hold your food.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Corollary: Every 80 carbs you eat is +1 lb of carry weight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, a high carb diet might have 500g of carbs, which is up to 2500g = 5.5
lbs. Vs a high fat diet, for which 2000 calories requires 222g of fat, which is
444g of carry, or just over 1 lb. So switching macros can cause a 4-5 lb drop
in weight. And presumably this happens basically instantly, not over time. Body
composition hardly changes, but the weight swings based on macro balance.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re like me, and you go out swinging before the first day of the diet,
you can go from 3000 calories carb-heavy pizza and beer, to below 1000
calories, mostly protein on day 1. (trust me I&amp;rsquo;ve done this a lot). It wouldn&amp;rsquo;t
be unreasonable to see a 10 lb drop in weight in the first few days.  And
believe me you feel smaller, but it&amp;rsquo;s not fat loss.&lt;/p&gt;
&lt;h3 id="leveling-off"&gt;Leveling off&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s probably obvious now why I &amp;ldquo;level&amp;rdquo; off. The question isn&amp;rsquo;t &amp;ldquo;Why doesn&amp;rsquo;t
the weight loss continue&amp;rdquo;, because there is no early functional weight loss.
Just a macro balance change causing changes to water content in my body.&lt;/p&gt;
&lt;p&gt;The more subtle problem is that my &lt;em&gt;expected&lt;/em&gt; rate of weight loss is based on
starting weight. And my calorie deficit and predicted weight all come from
that.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;m basically completely misrepresenting my weight as too high, then
expecting a steeper slope down from a given deficit. That is, my &lt;em&gt;basal
metabolic rate&lt;/em&gt; (calculated from weight) is much lower than I think because my
weight is 5-10% higher from water due to pizza and beer binges before my diet.&lt;/p&gt;
&lt;p&gt;All that to say, maybe third time is the charm.&lt;/p&gt;
&lt;h2 id="closing"&gt;Closing&lt;/h2&gt;
&lt;p&gt;To ensure that yes, this year I will actually do it, I mailed a large check to
a friend who can cash it if I do not stand on a scale in front of him by the
end of July and clock in at 200 lbs. I&amp;rsquo;m cautiously optimistic.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Wed, 12 Feb 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dieting-differential-equations-4.html</guid></item><item><title>What did you expect?  Of course the Trump administration will defy court orders!</title><link>https://blog.rongarret.info/2025/02/what-did-you-expect-of-course-trump.html</link><description>I'm not sure whether to laugh or cry over this:Federal judges have issued President Donald Trump
 stinging legal rebukes in the early clashes over his blitz of executive
 orders, and two of his top aides have responded by suggesting that his 
administration defy the courts and move forward with its agenda.There’s no indication that Trump has adopted such a strategy, although a U.S. judge in Rhode</description><author>Rondam Ramblings</author><pubDate>Tue, 11 Feb 2025 00:30:25 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/02/what-did-you-expect-of-course-trump.html</guid></item><item><title>Archiving hardware projects</title><link>http://blog.jgc.org/2025/02/archiving-hardware-projects.html</link><description>&lt;p&gt;From time to time I do some project involving old hardware that requires connecting it to a modern computer. For example,&lt;/p&gt;&lt;p&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;a href="https://blog.jgc.org/2025/02/getting-kim-1-to-talk-to-my-mac.html"&gt;Getting the KIM-1 to talk to my Mac&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="https://blog.jgc.org/2022/03/resurrecting-dataman-s4-prom-programmer.html"&gt;Resurrecting a Dataman S4 PROM programmer&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="https://blog.jgc.org/2022/04/ripping-old-mini-dv-video-tapes-on-mac.html"&gt;Ripping old mini DV video tapes on a Mac&lt;/a&gt;&lt;/p&gt;&lt;p&gt;And I've come to the conclusion that archiving the related hardware is important. For example, I got this Dataman S4 talking to my Mac using a few different cables. I bought a set of cables and archived them in a bag for the next time I need to do this.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_A37qHS8SIBr146GTQt5yyNg3AENYhRcyj6vAbQCCJeCaSpXI5Mh8IiMRhQMYLGqtqoIeGhgFK5VarUgScWfPV_jlhcyZEXdbtcjUCIgpdz6WG903sxPYD6g9c3rG3hyphenhyphenNcf06m0cyeKQn1OkAVMy_4fFLoiDbNAs1Z2KhlFET2S_rDJY9yZpc1w/s2664/dataman5.jpg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_A37qHS8SIBr146GTQt5yyNg3AENYhRcyj6vAbQCCJeCaSpXI5Mh8IiMRhQMYLGqtqoIeGhgFK5VarUgScWfPV_jlhcyZEXdbtcjUCIgpdz6WG903sxPYD6g9c3rG3hyphenhyphenNcf06m0cyeKQn1OkAVMy_4fFLoiDbNAs1Z2KhlFET2S_rDJY9yZpc1w/w573-h640/dataman5.jpg" width="573" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;The Dataman S4 project required three things: a USB-C to USB-A adapter, a USB-A to RS-232 adapter and a 9 pin to 25 pin serial cable.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBw-gv1T6yK7NS-YQn7wHnDNK61-ow4R6AYjWETsNLG-IQ7pXIcni3NQg0MpcFWKaNyTE6edLK3whoeDL_cvm5M4DzwHm7ytq3zngw5GAwEM3I4wfHlLZdXIruTlCLbpooZi9FZPOQ5Axdq-O8IqUVOFbVpRmNR8NZlE9-Y39vvjUta7XR3UgUug/s4234/archive-1.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBw-gv1T6yK7NS-YQn7wHnDNK61-ow4R6AYjWETsNLG-IQ7pXIcni3NQg0MpcFWKaNyTE6edLK3whoeDL_cvm5M4DzwHm7ytq3zngw5GAwEM3I4wfHlLZdXIruTlCLbpooZi9FZPOQ5Axdq-O8IqUVOFbVpRmNR8NZlE9-Y39vvjUta7XR3UgUug/w590-h640/archive-1.jpeg" width="590" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These things are fairly inexpensive making it viable to archive them. Now I only have to worry about the drivers being available!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8oe93fugNv6GtBjc9-RkAdaU3jo5Cruy6-BxJH-5uX_k7eFtKFqTBODWC9VXHVSBWRRAHNt5djl1DSNlJ3-mzuSuWZYFR_kK0R7gGSH1zxuL87xhen5ysQTBmh7shP0t4yR4iVQJXXqPsGSI3cSFlQdrROEWokFWiCNCcYNxO0NiQrp4a1T5xfw/s4083/archive-2.jpeg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8oe93fugNv6GtBjc9-RkAdaU3jo5Cruy6-BxJH-5uX_k7eFtKFqTBODWC9VXHVSBWRRAHNt5djl1DSNlJ3-mzuSuWZYFR_kK0R7gGSH1zxuL87xhen5ysQTBmh7shP0t4yR4iVQJXXqPsGSI3cSFlQdrROEWokFWiCNCcYNxO0NiQrp4a1T5xfw/w590-h640/archive-2.jpeg" width="590" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Mon, 10 Feb 2025 19:09:09 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/02/archiving-hardware-projects.html</guid></item><item><title>Multiline fixed string search and replace with CLI tools</title><link>https://learnbyexample.github.io/multiline-search-and-replace/</link><description>&lt;p&gt;This post shows how you can use the &lt;code&gt;ripgrep&lt;/code&gt;, &lt;code&gt;perl&lt;/code&gt; and &lt;code&gt;sd&lt;/code&gt; commands to perform multiline fixed string search and replace operations from the command line. Solutions with &lt;code&gt;GNU sed&lt;/code&gt; is also discussed, along with its limitations.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="fixed-string-matching"&gt;Fixed string matching&lt;a class="zola-anchor" href="#fixed-string-matching"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The below sample input file will be used in the examples in this post.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="ripgrep"&gt;ripgrep&lt;a class="zola-anchor" href="#ripgrep"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; supports the &lt;code&gt;-U&lt;/code&gt; option to allow multiline matching. The &lt;code&gt;-F&lt;/code&gt; option turns off regexp matching, i.e. the search string is treated literally. In the &lt;code&gt;bash&lt;/code&gt; shell (and likely most other shells), you can press enter key to insert literal newline character for quoted values. When you do so, the next line starts with the secondary prompt &lt;code&gt;PS2&lt;/code&gt;, which is usually &lt;code&gt;&amp;gt;&lt;/code&gt; and a space character. This isn't shown in the examples below to make it easier to copy-paste the commands.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ rg -UF 'like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and' ip.txt
&lt;/span&gt;&lt;span&gt;4:like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;5:| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# use the -l option to get only the filename instead of matching lines
&lt;/span&gt;&lt;span&gt;$ rg -lUF 'like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and' ip.txt
&lt;/span&gt;&lt;span&gt;ip.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll have an issue if your search string itself contains single quote characters. Avoid using double quotes as a workaround, as that has its own set of special characters. You can work around by concatenating multiple strings next to each other, along with escaped single quote characters as needed.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;# the -N option disables line number prefix
&lt;/span&gt;&lt;span&gt;$ rg -NUF 'like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and '\'' and' ip.txt
&lt;/span&gt;&lt;span&gt;like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your search string starts with the &lt;code&gt;-&lt;/code&gt; character, you'll have to use &lt;code&gt;--&lt;/code&gt; before the search argument.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ rg -NUF -- '-string multiline
&lt;/span&gt;&lt;span&gt;search' ip.txt
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="perl"&gt;perl&lt;a class="zola-anchor" href="#perl"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can use the &lt;code&gt;-0777&lt;/code&gt; option with &lt;code&gt;perl&lt;/code&gt; to slurp the entire input as a single string. Another advantage with &lt;code&gt;perl&lt;/code&gt; is that you can use files to pass the search and replace strings. Thus, you don't have to worry about any character that may clash with shell metacharacters. See my &lt;a href="https://learnbyexample.github.io/learn_perl_oneliners/"&gt;Perl One-Liners Guide&lt;/a&gt; if you are not familiar with using &lt;code&gt;perl&lt;/code&gt; from the command line.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat search_1.txt
&lt;/span&gt;&lt;span&gt;like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# display filename if the given search string matches
&lt;/span&gt;&lt;span&gt;$ perl -0777 -nE '!$#ARGV ? $s=$_ :
&lt;/span&gt;&lt;span&gt;                  /\Q$s/ &amp;amp;&amp;amp; say $ARGV' search_1.txt ip.txt
&lt;/span&gt;&lt;span&gt;ip.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, you'll have to make sure the file doesn't end with a newline if you are providing partial lines for searching, or take care of it within the &lt;code&gt;perl&lt;/code&gt; script.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat search_2.txt
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# no output because there's a newline at the end of search_2.txt file
&lt;/span&gt;&lt;span&gt;$ perl -0777 -nE '!$#ARGV ? $s=$_ :
&lt;/span&gt;&lt;span&gt;                  /\Q$s/ &amp;amp;&amp;amp; say $ARGV' search_2.txt ip.txt
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# this will remove newline from the end of file before assigning to $s
&lt;/span&gt;&lt;span&gt;$ perl -0777 -nE '!$#ARGV ? $s=s/\n\z//r :
&lt;/span&gt;&lt;span&gt;                  /\Q$s/ &amp;amp;&amp;amp; say $ARGV' search_2.txt ip.txt
&lt;/span&gt;&lt;span&gt;ip.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, &lt;code&gt;ripgrep&lt;/code&gt; gives entire matching lines. To get rest of the line with &lt;code&gt;perl&lt;/code&gt;, you'll have to explicitly add a pattern around the search string.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;# $&amp;amp; variable has the entire matching portion
&lt;/span&gt;&lt;span&gt;$ perl -0777 -nE '!$#ARGV ? $s=s/\n\z//r :
&lt;/span&gt;&lt;span&gt;                  /\Q$s/ &amp;amp;&amp;amp; say $&amp;amp;' search_2.txt ip.txt
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# use 'say $&amp;amp; while /.*\Q$s\E.*/g' if there are multiple matches
&lt;/span&gt;&lt;span&gt;$ perl -0777 -nE '!$#ARGV ? $s=s/\n\z//r :
&lt;/span&gt;&lt;span&gt;                  /.*\Q$s\E.*/ &amp;amp;&amp;amp; say $&amp;amp;' search_2.txt ip.txt
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="fixed-string-substitution"&gt;Fixed string substitution&lt;a class="zola-anchor" href="#fixed-string-substitution"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="ripgrep-1"&gt;ripgrep&lt;a class="zola-anchor" href="#ripgrep-1"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ripgrep&lt;/code&gt; also supports replacing the matched string with something else using the &lt;code&gt;-r&lt;/code&gt; option. By default, you'll see only matched lines in the output. Use the &lt;code&gt;--passthru&lt;/code&gt; option to display all the input lines, even if they do not match the given search string. See &lt;a href="https://learnbyexample.github.io/substitution-with-ripgrep/"&gt;my blog post&lt;/a&gt; for more details about the &lt;code&gt;-r&lt;/code&gt; option and various ways you can use it for substitution requirements.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ rg --passthru -NUF 'like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and' -r '====
&lt;/span&gt;&lt;span&gt;----
&lt;/span&gt;&lt;span&gt;====' ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;====
&lt;/span&gt;&lt;span&gt;----
&lt;/span&gt;&lt;span&gt;==== ' and so on.
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apart from having to workaround single quote, you'll have to use &lt;code&gt;$$&lt;/code&gt; instead of &lt;code&gt;$&lt;/code&gt; as it is used for backreferences in the replacement section.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ echo 'sample input' | rg --passthru -F 'in' -r '$a'
&lt;/span&gt;&lt;span&gt;sample put
&lt;/span&gt;&lt;span&gt;$ echo 'sample input' | rg --passthru -F 'in' -r '$$a'
&lt;/span&gt;&lt;span&gt;sample $aput
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="perl-1"&gt;perl&lt;a class="zola-anchor" href="#perl-1"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With &lt;code&gt;perl&lt;/code&gt;, you can use files for both the search and replace strings. And, you can easily choose to replace the first or all occurrences, unlike &lt;code&gt;ripgrep&lt;/code&gt; where it always replaces all the matches.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat replace.txt
&lt;/span&gt;&lt;span&gt;---------------------
&lt;/span&gt;&lt;span&gt;$&amp;amp; = $1 + $2 / 3 \ 4
&lt;/span&gt;&lt;span&gt;=====================
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ perl -0777 -ne '$#ARGV==1 ? $s=$_ : $#ARGV==0 ? $r=$_ :
&lt;/span&gt;&lt;span&gt;                  print s/\Q$s/$r/gr' search_1.txt replace.txt ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;---------------------
&lt;/span&gt;&lt;span&gt;$&amp;amp; = $1 + $2 / 3 \ 4
&lt;/span&gt;&lt;span&gt;=====================
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As seen before, you'll have to remove newline from the search string for partial line matching.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;# use $r=s/\n\z//r to avoid trailing newline from replace.txt
&lt;/span&gt;&lt;span&gt;$ perl -0777 -ne '$#ARGV==1 ? $s=s/\n\z//r : $#ARGV==0 ? $r=$_ :
&lt;/span&gt;&lt;span&gt;                  print s/\Q$s/$r/gr' search_2.txt replace.txt ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;---------------------
&lt;/span&gt;&lt;span&gt;$&amp;amp; = $1 + $2 / 3 \ 4
&lt;/span&gt;&lt;span&gt;=====================
&lt;/span&gt;&lt;span&gt; with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="sd"&gt;sd&lt;a class="zola-anchor" href="#sd"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/chmln/sd"&gt;sd&lt;/a&gt; supports a fixed string option and Rust regexp based substitution. Unlike &lt;code&gt;ripgrep&lt;/code&gt;, the &lt;code&gt;-s&lt;/code&gt; option for fixed string will apply to both the search and replacement sections. &lt;code&gt;sd&lt;/code&gt; does in-place editing for file inputs by default, you can use &lt;code&gt;-p&lt;/code&gt; to preview results on the terminal. Multiline matching is automatically performed by default.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ echo 'sample input' | sd -s 'in' '$a'
&lt;/span&gt;&lt;span&gt;sample $aput
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ sd -ps 'like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and' '====
&lt;/span&gt;&lt;span&gt;----
&lt;/span&gt;&lt;span&gt;====' ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;====
&lt;/span&gt;&lt;span&gt;----
&lt;/span&gt;&lt;span&gt;==== ' and so on.
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="saving-file-contents-to-a-variable"&gt;Saving file contents to a variable&lt;a class="zola-anchor" href="#saving-file-contents-to-a-variable"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Trailing newlines and ASCII NUL characters will be lost if you wish to save contents of a file as &lt;code&gt;bash&lt;/code&gt; variables using the &lt;code&gt;var=$(&amp;lt; filename)&lt;/code&gt; command. See &lt;a href="https://stackoverflow.com/a/22607352/4082052"&gt;stackoverflow: pitfalls of reading file into shell variable&lt;/a&gt; for more details.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ printf '\na\0b\n123\n\n\n\n\n\n\n\n' &amp;gt; t1
&lt;/span&gt;&lt;span&gt;$ a=$(&amp;lt; t1)
&lt;/span&gt;&lt;span&gt;bash: warning: command substitution: ignored null byte in input
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# NUL character is lost after the assignment
&lt;/span&gt;&lt;span&gt;# all the trailing newlines are lost as well
&lt;/span&gt;&lt;span&gt;$ printf '%b' &amp;quot;$a&amp;quot; | cat -A
&lt;/span&gt;&lt;span&gt;$
&lt;/span&gt;&lt;span&gt;ab$
&lt;/span&gt;&lt;span&gt;123
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="ripgrep-2"&gt;ripgrep&lt;a class="zola-anchor" href="#ripgrep-2"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If your search string doesn't have multiple trailing newlines or ASCII NUL characters, then you can save file contents to variables and then pass them to &lt;code&gt;ripgrep&lt;/code&gt;. Single trailing newline will not normally cause an issue for searching operations as &lt;code&gt;ripgrep&lt;/code&gt; will append a newline while displaying results anyway. If you want to make sure input file also contains the trailing newline, then you can manually concatenate a newline character to the search string.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ s=$(&amp;lt; search_1.txt)
&lt;/span&gt;&lt;span&gt;# use &amp;quot;$s&amp;quot;$'\n' if you want to match trailing newline as well
&lt;/span&gt;&lt;span&gt;$ rg -NUF &amp;quot;$s&amp;quot; ip.txt
&lt;/span&gt;&lt;span&gt;like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# use -- if the search string starts with a - character
&lt;/span&gt;&lt;span&gt;$ s=$(&amp;lt; search_2.txt)
&lt;/span&gt;&lt;span&gt;$ rg -NUF -- &amp;quot;$s&amp;quot; ip.txt
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For substitution operations, you'll have to preprocess the replacement file to replace &lt;code&gt;$&lt;/code&gt; with &lt;code&gt;$$&lt;/code&gt;.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ s=$(&amp;lt; search_1.txt)
&lt;/span&gt;&lt;span&gt;$ r=$(sed 's/\$/$$/g' replace.txt)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# here, removal of trailing newline doesn't cause an issue,
&lt;/span&gt;&lt;span&gt;# as it evens out between the search and replace strings
&lt;/span&gt;&lt;span&gt;$ rg --passthru -NUF &amp;quot;$s&amp;quot; -r &amp;quot;$r&amp;quot; ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;---------------------
&lt;/span&gt;&lt;span&gt;$&amp;amp; = $1 + $2 / 3 \ 4
&lt;/span&gt;&lt;span&gt;=====================
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, partial line has to be matched. So, &lt;code&gt;$()&lt;/code&gt; assignment works well for the search string. If the trailing newline of the replacement string isn't needed, then &lt;code&gt;$()&lt;/code&gt; assignment again is good enough. Otherwise, you can modify the replacement string as &lt;code&gt;-r &amp;quot;$r&amp;quot;$'\n'&lt;/code&gt;&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ s=$(&amp;lt; search_2.txt)
&lt;/span&gt;&lt;span&gt;$ r=$(sed 's/\$/$$/g' replace.txt)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ rg --passthru -NUF -r &amp;quot;$r&amp;quot; -- &amp;quot;$s&amp;quot; ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;---------------------
&lt;/span&gt;&lt;span&gt;$&amp;amp; = $1 + $2 / 3 \ 4
&lt;/span&gt;&lt;span&gt;===================== with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="sd-1"&gt;sd&lt;a class="zola-anchor" href="#sd-1"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As mentioned before, the &lt;code&gt;-s&lt;/code&gt; option for &lt;code&gt;sd&lt;/code&gt; applies to both the search and replacement sections. So, the usage is lot simpler compared to &lt;code&gt;ripgrep&lt;/code&gt;.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;# -- is needed here because replace.txt starts with a - character
&lt;/span&gt;&lt;span&gt;$ sd -ps -- &amp;quot;$(&amp;lt; search_1.txt)&amp;quot; &amp;quot;$(&amp;lt; replace.txt)&amp;quot; ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;---------------------
&lt;/span&gt;&lt;span&gt;$&amp;amp; = $1 + $2 / 3 \ 4
&lt;/span&gt;&lt;span&gt;=====================
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;-string multiline
&lt;/span&gt;&lt;span&gt;search with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="gnu-sed"&gt;GNU sed&lt;a class="zola-anchor" href="#gnu-sed"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To follow a similar approach with &lt;code&gt;GNU sed&lt;/code&gt;, you'll have to preprocess the strings to escape metacharacters. Assuming input doesn't have ASCII NUL characters, you can use &lt;code&gt;-z&lt;/code&gt; option to slurp the entire input as a single string.&lt;/p&gt;
&lt;p&gt;Here's an example for multiline search.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;# escape all BRE metacharacters
&lt;/span&gt;&lt;span&gt;# replace literal newlines with \n
&lt;/span&gt;&lt;span&gt;$ s=$(sed -z 's#[[^$*.\/]#\\&amp;amp;#g; s/\n/\\n/g' search_1.txt)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# since newlines are replaced with \n,
&lt;/span&gt;&lt;span&gt;# trailing newlines will be preserved here
&lt;/span&gt;&lt;span&gt;$ echo &amp;quot;$s&amp;quot;
&lt;/span&gt;&lt;span&gt;like \. () \* \[] \$ {}\n| \^ + ? \\ and ' and so on\.\n
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# display filename if input matches the given multiline search string
&lt;/span&gt;&lt;span&gt;# tr is used to change the NUL character after filename to newline
&lt;/span&gt;&lt;span&gt;$ sed -nz '/'&amp;quot;$s&amp;quot;'/F' ip.txt | tr '\0' '\n'
&lt;/span&gt;&lt;span&gt;ip.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here's an example for multiline substitution.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;# last newline is removed here to allow partial line matching
&lt;/span&gt;&lt;span&gt;$ s=$(sed -z 's#[[^$*.\/]#\\&amp;amp;#g; s/\n$//; s/\n/\\n/g' search_2.txt)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# escape all replacement section metacharacters
&lt;/span&gt;&lt;span&gt;# and prefix \ character to literal newlines, except the last line
&lt;/span&gt;&lt;span&gt;$ r=$(sed 's:[\\/&amp;amp;]:\\&amp;amp;:g; $!s/$/\\/' replace.txt)
&lt;/span&gt;&lt;span&gt;$ echo &amp;quot;$r&amp;quot;
&lt;/span&gt;&lt;span&gt;---------------------\
&lt;/span&gt;&lt;span&gt;$\&amp;amp; = $1 + $2 \/ 3 \\ 4\
&lt;/span&gt;&lt;span&gt;=====================
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;# if you need the trailing newline from replace.txt,
&lt;/span&gt;&lt;span&gt;# use sed -z 's/'&amp;quot;$s&amp;quot;'/'&amp;quot;$r&amp;quot;'\n/g'
&lt;/span&gt;&lt;span&gt;$ sed -z 's/'&amp;quot;$s&amp;quot;'/'&amp;quot;$r&amp;quot;'/g' ip.txt
&lt;/span&gt;&lt;span&gt;This is a multiline
&lt;/span&gt;&lt;span&gt;sample input with lots
&lt;/span&gt;&lt;span&gt;of special characters
&lt;/span&gt;&lt;span&gt;like . () * [] $ {}
&lt;/span&gt;&lt;span&gt;| ^ + ? \ and ' and so on.
&lt;/span&gt;&lt;span&gt;This post shows how
&lt;/span&gt;&lt;span&gt;you can do fixed
&lt;/span&gt;&lt;span&gt;---------------------
&lt;/span&gt;&lt;span&gt;$&amp;amp; = $1 + $2 / 3 \ 4
&lt;/span&gt;&lt;span&gt;===================== with cli tools.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="linux-cli-ebooks"&gt;Linux CLI ebooks&lt;a class="zola-anchor" href="#linux-cli-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href="https://learnbyexample.github.io/books/"&gt;my ebooks&lt;/a&gt; if you are interested in learning more about Linux CLI basics, coreutils, text processing tools like &lt;code&gt;GNU grep&lt;/code&gt;, &lt;code&gt;GNU sed&lt;/code&gt;, &lt;code&gt;GNU awk&lt;/code&gt; and &lt;code&gt;perl&lt;/code&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 10 Feb 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/multiline-search-and-replace/</guid></item><item><title>Agentic Coding - Live Demo / Brownbag</title><link>https://smcleod.net/2025/02/agentic-coding-live-demo-/-brownbag/</link><description>Video recording of a brownbag presentation / live demo I ran on Agentic Coding using Cline/Roo Code</description><author>smcleod.net</author><pubDate>Thu, 06 Feb 2025 17:00:01 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2025/02/agentic-coding-live-demo-/-brownbag/</guid></item><item><title>Twenty years of the "GNU Make Standard Library"</title><link>http://blog.jgc.org/2025/02/twenty-years-of-gnu-make-standard.html</link><description>&lt;p&gt;Twenty years ago, on February 3, 2005, I released v1.0.0 of a project I called "GNU Make Standard Library" (GMSL). That &lt;a href="https://sourceforge.net/projects/gmsl/files/GNU%20Make%20Standard%20Library/v1.0.0/"&gt;first release&lt;/a&gt; can still be found in its original location on SourceForge. I moved the project from SourceForge to &lt;a href="https://github.com/jgrahamc/gmsl"&gt;GitHub&lt;/a&gt; and newer releases are there. The latest release is &lt;a href="https://github.com/jgrahamc/gmsl/releases/tag/v1.2.2"&gt;v1.2.2&lt;/a&gt; on March 30, 2024.&lt;/p&gt;&lt;p&gt;The project has its own homepage at&amp;nbsp;&lt;a href="https://gmsl.jgc.org/"&gt;https://gmsl.jgc.org/&lt;/a&gt;. The page describes the GMSL as "a collection of functions implemented using native GNU Make functionality that provide list and string manipulation, integer arithmetic, associative arrays, stacks, and debugging facilities."&lt;/p&gt;&lt;p&gt;GMSL grew out of work that eventually became my book "&lt;a href="https://nostarch.com/gnumake"&gt;The GNU Make Book&lt;/a&gt;". What can it do?&lt;/p&gt;&lt;p&gt;Well, things like this:&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;include gmsl&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;&lt;br /&gt;VAR&amp;nbsp; &amp;nbsp; := Here is a string with some words in it.&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(VAR))&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;&lt;br /&gt;# Convert VAR to lowercase&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;LC_VAR := $(call lc,$(VAR))&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(LC_VAR))&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;&lt;br /&gt;# Get the length of LC_VAR as a string&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(call strlen,$(LC_VAR)))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;# See if VAR and LC_VAR are the same string&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(if $(call seq,$(VAR),$(LC_VAR)),Same,Different))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;# Treat LC_VAR as a list and get the last element&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(call last,$(LC_VAR)))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;# Get the number of list elements in LC_VAR&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(call length,$(LC_VAR)))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;# Apply the strlen function to each element of LC_VAR&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(call map,strlen,$(LC_VAR)))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;# Deduplicate the list of lenghts of elements of LC_VAR&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(call uniq,$(call map,strlen,$(LC_VAR))))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;# Split LC_VAR on the letter e and replace with 3&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(info $(call merge,3,$(call split,e,$(LC_VAR))))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;# Create an associative array mapping the lengths of words&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;# in LC_VAR to a count for each length&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;increment_aa = $(call set,LC_VAR_AA,$1,$(call&amp;nbsp;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;inc,$(call&amp;nbsp;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;get,LC_VAR_AA,$1)))&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(call map,increment_aa,$(call map,strlen,$(LC_VAR)))&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;dump_key = $(info $1: $(call get,LC_VAR_AA,$1))&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier;"&gt;$(call map,dump_key,$(call keys,LC_VAR_AA))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;And a whole lot more. The entire GMSL is written using native GNU Make functionality and pull requests are welcome!&lt;/p&gt;&lt;p&gt;Update 2025-02-05: I decided GMSL was missing a &lt;a href="https://github.com/jgrahamc/gmsl/blob/145bda7295dee873b2c2842f404365857ea24764/__gmsl#L239"&gt;fold&lt;/a&gt; function so I added it. So, latest release is today, &lt;a href="https://github.com/jgrahamc/gmsl/releases/tag/v1.2.3"&gt;v1.2.3&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Wed, 05 Feb 2025 11:50:10 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2025/02/twenty-years-of-gnu-make-standard.html</guid></item><item><title>Giving Junior Engineers Control Of A Six Trillion Dollar System Is Nuts</title><link>https://thecodist.com/giving-junior-engineers-control-of-a-six-trillion-dollar-system-is-nuts/</link><description>&lt;p&gt;For some purpose, the DOGE people are burrowing their way into all US Federal Systems. Their complete control over the Treasury Department is entirely insane.&lt;/p&gt;&lt;p&gt;Unless you intend to destroy everything, making arbitrary changes to complex computer systems will result in destruction, even if that was not your intention. No&lt;/p&gt;</description><author>The Codist</author><pubDate>Wed, 05 Feb 2025 01:05:17 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/giving-junior-engineers-control-of-a-six-trillion-dollar-system-is-nuts/</guid></item><item><title>Practicing graphical debugging using too many visualizations of the Hilbert curve</title><link>http://akkartik.name/post/debugUIs</link><description>&lt;p&gt;
Sorry, this article is too wide for my current website design so you'll need to &lt;a href="/debugUIs.html"&gt;go to it &amp;rarr;&lt;/a&gt;

&lt;p&gt;
&lt;img alt="A single blue continuous fractal space-filling Hilbert curve made of straight lines bending in perpendicular corners" src="/images/20250129-debugUIs/v1.png" style="float: none; margin-left: 2em; width: 80%;" /&gt;

&lt;p&gt;</description><author>Kartik Agaram</author><pubDate>Fri, 31 Jan 2025 19:06:44 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/debugUIs</guid></item><item><title>Edit for clarity</title><link>http://notes.eatonphil.com/2025-01-29-edit-for-clarity.html</link><description>&lt;p&gt;I have the fortune to review a
&lt;a href="https://eatonphil.com/editor.html"&gt;few&lt;/a&gt; important blog posts every year and
the biggest value I add is to call out sentences or sections that make
no sense. It is quite simple and you can do it too.&lt;/p&gt;
&lt;p&gt;Without clarity only those at your company in marketing and sales
(whose job it is to work with what they get) will give you the
courtesy of a cursory read and a like on LinkedIn. This is all that
most corporate writing achieves. It is the norm and it is understandable.&lt;/p&gt;
&lt;p&gt;But if you want to reach an audience beyond those folks, you have to
make sure you're not writing nonsense. And you, as reviewer and
editor, have the chance to call out nonsense if you can get yourself
to recognize it.&lt;/p&gt;
&lt;h3 id="immune-to-nonsense"&gt;Immune to nonsense&lt;/h3&gt;&lt;p&gt;But especially when editing blog posts at work, it is easy to gloss
over things that make no sense because we are so constantly
bombarded by things that make no sense. Maybe it's buzzwords or
cliches, or simply lack of rapport. We become immune to nonsense.&lt;/p&gt;
&lt;p&gt;And even worse, without care, as we become more experienced, we become
more fearful to say "I have no idea what you are talking about". We're
afraid to look incompetent by admitting our confusion. This fear is
understandable, but is itself stupid. And I will trust you to deal
with this on your own.&lt;/p&gt;
&lt;h3 id="read-it-out-loud"&gt;Read it out loud&lt;/h3&gt;&lt;p&gt;So as you review a post, read it out loud to yourself. And if you find
yourself saying "what on earth are you talking about", add that as a
comment as gently as you feel you should. It is not offensive to say
this (depending on how you say it). It is surely the case that the
author did not know they were making no sense. It is worse to not
mention your confusion and allow the author to look like an idiot or a
bore.&lt;/p&gt;
&lt;p&gt;Once you can call out what does not make sense to you, then read the
post again and consider what would not make sense to someone without
the context you have. Someone outside your company. Of course you need
to make assumptions about the audience to a degree. It is likely your
customers or prospects you have in mind. Not your friends or family.&lt;/p&gt;
&lt;p&gt;With the audience you have in mind, would what you're reading make
any sense? Has the author given sufficient background or introduced
relevant concepts before bringing up something new?&lt;/p&gt;
&lt;p&gt;Again this is a second step though. The first step is to make sure
that the post makes sense to &lt;em&gt;you&lt;/em&gt;. In almost every draft I read, at my
company or not, there is something that does not make sense to me.&lt;/p&gt;
&lt;p&gt;Do two paragraphs need to be reordered because the first one
accidentally depended on information mentioned in the second? Are you
making ambiguous use of pronouns? And so on.&lt;/p&gt;
&lt;h3 id="in-closing"&gt;In closing&lt;/h3&gt;&lt;p&gt;Clarity on its own will put you in the 99th percentile of
writing. Beyond that it definitely still matters if you are compelling and
original and whatnot. But too often it seems we focus on being
exciting rather than being clear. But it doesn't matter if you've got
something exciting if it makes no sense to your reader.&lt;/p&gt;
&lt;p&gt;This sounds like mundane guidance, but I have reviewed many posts that
were reviewed by other people and no one else called out nonsense. I
feel compelled to mention how important it is.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a new post on the most important, and perhaps least done, thing you can do while reviewing a blog post: edit for clarity. &lt;a href="https://t.co/ODblOUzB3g"&gt;pic.twitter.com/ODblOUzB3g&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1884735729625952692?ref_src=twsrc%5Etfw"&gt;January 29, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 29 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-01-29-edit-for-clarity.html</guid></item><item><title>Basic examples for the Linux date command</title><link>https://learnbyexample.github.io/mini/linux-date-command-examples/</link><description>&lt;p&gt;I rarely ever use the &lt;code&gt;date&lt;/code&gt; command, but when I need it I almost always struggle to get the right incantation. So, I'm just going to record such examples in this blog post (and some good to know features).&lt;/p&gt;
&lt;p&gt;There'll also be learning resources linked at the end of the post.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="really-basic-examples"&gt;Really basic examples&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;date&lt;/code&gt; command by itself shows the current time. But that's rarely what I need, since I could just use the calendar widget at the bottom of my desktop screen. Perhaps useful to copy the string format and modify system time with the &lt;code&gt;-s&lt;/code&gt; option?&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# use the -u option for UTC (Coordinated Universal Time)
&lt;/span&gt;&lt;span&gt;$ date
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Wednesday &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;31 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;July &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024 03&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;01 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;PM IST
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, I need particular parts in a particular format. For example, to represent the time component in a dynamically constructed filename as part of a shell script.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# same as: date +%F or date -I or date --iso-8601
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+%&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Y&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-%&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-%&lt;/span&gt;&lt;span&gt;d
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;07&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;31
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+%&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Y&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/%m/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%&lt;/span&gt;&lt;span&gt;d
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;07&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;31
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+%&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-%&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-%&lt;/span&gt;&lt;span&gt;d
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;24&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;07&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;31
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use 'b' and 'B' for month names
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+%&lt;/span&gt;&lt;span&gt;a
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Wed
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+%&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;A
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Wednesday
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use &lt;code&gt;%x&lt;/code&gt; to get the locale representation:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;%x
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;31/07/24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For hours, minutes and seconds:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# same as: date +%T
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+%&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;H:&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;M:&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;S
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;16&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;00&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;32
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: date +%Y-%m-%dT%H:%M:%S%:z
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Iseconds
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;07&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;31T16&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;09&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;27&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;05&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="displaying-and-converting-epoch-seconds"&gt;Displaying and converting epoch seconds&lt;/h2&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# total seconds since the epoch (1970-01-01 00:00:00 UTC)
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;%s
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;1722422393
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d @&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1722422393 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%F %T&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;07&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;31 16&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;09&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also provide an input file for conversion using the &lt;code&gt;-f&lt;/code&gt; option:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat epochs.txt
&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0000000000
&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1234567890
&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2222222222
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# recall that the -u option gives you UTC
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;u &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;f epochs.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%F %T&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1970&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;01&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;01 00&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;00&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;00
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2009&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;02&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;13 23&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;31&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;30
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2040&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;06&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;02 03&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;57&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;02
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="date-arithmetic"&gt;Date arithmetic&lt;/h2&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;I
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;08&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;02
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'+1 month 4 days'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Friday &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;06 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;September &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024 01&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;24&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;44 &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;PM IST
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: date -d '-20 days' +%F
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# you can also use '20 days ago'
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;I &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'-20 days'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;07&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For my &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; newsletter, I use a script to generate a template issue. I use the arithmetic feature as shown below:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# prev_date variable gets the value from the previous newsletter issue
&lt;/span&gt;&lt;span&gt;$ prev_date=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2024-02-23'
&lt;/span&gt;&lt;span&gt;$ date &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$prev_date&amp;quot;' +7 days' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+%&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2024&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;03&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;01
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="resource-links"&gt;Resource links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/coreutils/manual/coreutils.html#date-invocation"&gt;GNU date command manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/coreutils/manual/coreutils.html#Examples-of-date"&gt;&amp;quot;Examples of date&amp;quot; section from the manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unix.stackexchange.com/questions/tagged/date?tab=Votes"&gt;Top voted Q&amp;amp;A on unix.stackexchange for the date command tag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Tue, 28 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/mini/linux-date-command-examples/</guid></item><item><title>The CP/M emulator now works better!</title><link>https://blog.steve.fi/the_cp_m_emulator_now_works_better_.html</link><description>&lt;p&gt;I keep saying I'm "done" with my &lt;a href="https://github.com/skx/cpmulator" rel="nofollow"&gt;CP/M emulator&lt;/a&gt;, but then I keep overhauling it in significant ways.  Today is no exception.  In the past the emulator used breakpoints to detect when calls to the system BIOS, or BDOS, were made.  That was possible because the BIOS and BDOS entry points are at predictable locations.  For example a well-behaved program might make a system call with code like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    LD A,42
    LD C,4
    CALL 0x0005
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So setting a breakpoint on 0x0005 would let you detect a system-call was being made, inspect the registers to see &lt;em&gt;which&lt;/em&gt; system-call was being made and then carry out the appropriate action in your emulator before returning control back to the program.  Unfortunately some binaries patch the RAM, changing the contents of the entry points, or changing internal jump-tables, etc.  The end result is that &lt;em&gt;sometimes&lt;/em&gt; code running at the fixed addresses is not your BIOS at all, but something else.  By trapping/faulting/catching execution here you break things, badly.&lt;/p&gt;

&lt;p&gt;So today's new release fixes that!  No more breakpoints.  Instead we deploy a "real BDOS" in RAM that will route system-calls to our host emulator via a clever trick.  For BDOS functions the C-register will contain the system call to operate, our complete BDOS implementation is:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    OUT (C),C
    RET
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The host program can catch writes to output ports, and will know that "OUT (3), 3" means "Invoke system call #3", for example.  This means binary patches to entry-points, or any internal jump-tables won't confuse things and so long as control eventually reaches my BIOS or BDOS code areas things will work.&lt;/p&gt;

&lt;p&gt;I &lt;em&gt;also&lt;/em&gt; added a new console-input driver, since I have a notion of pluggable input and output devices, which just reads input from a file.  Now I can prove that my code works.  Pass the following file to the input-driver and we have automated testing:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;A:
ERA HELLO.COM
ERA HELLO.HEX
ERA HELLO.PRN
hello
ASM HELLO
LOAD HELLO
DDT HELLO.com
t
t
t
t
t
t
t
t
t
C-c
EXIT
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Erase "HELLO.COM", "HELLO.HEX", "HELLO.PRN"&lt;/li&gt;
&lt;li&gt;Invoke "hello[.com]" (which will fail, as we've just deleted it).&lt;/li&gt;
&lt;li&gt;Then we assemble "HELLO.ASM" to "HELLO.HEX", then to "HELLO.COM"&lt;/li&gt;
&lt;li&gt;Invoke DDT, the system debugger, and tell it to trace execution a bunch of times.&lt;/li&gt;
&lt;li&gt;Finally we exit the debugger with "Ctrl-C"&lt;/li&gt;
&lt;li&gt;And exit the emulator with "exit"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can test the output and confirm there are no regressions.  Neat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/skx/cpmulator/releases" rel="nofollow"&gt;Anyway new release, today&lt;/a&gt;.  Happy.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Sun, 26 Jan 2025 01:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/the_cp_m_emulator_now_works_better_.html</guid></item><item><title>How to download YouTube Videos quickly</title><link>https://www.swyx.io/youtube-download</link><description>&lt;p&gt;I used to use yt5s all the time to rip and remix videos:&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Sat, 25 Jan 2025 20:33:59 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/youtube-download</guid></item><item><title>What I Miss And Don't From Working As A Programmer</title><link>https://thecodist.com/what-i-miss-and-dont-from-working-as-a-programmer/</link><description>&lt;p&gt;I retired almost four years ago after nearly 40 years as a programmer. While I still write code daily, I do so to support &lt;a href="https://andrewwulf.com/?ref=thecodist.com" rel="noreferrer"&gt;my generative art&lt;/a&gt; rather than get paid for it.&lt;/p&gt;&lt;p&gt;Most of my career was spent building new applications, and no matter what my title was, I&lt;/p&gt;</description><author>The Codist</author><pubDate>Sat, 25 Jan 2025 04:36:14 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/what-i-miss-and-dont-from-working-as-a-programmer/</guid></item><item><title>An explosion of transitive dependencies</title><link>http://notes.eatonphil.com/2025-01-25-an-explosion-of-transitive-dependencies.html</link><description>&lt;p&gt;A small standard library means an explosion in transitive
dependencies. A more comprehensive standard library helps you minimize
dependencies. Don't misunderstand me: in a real-world project, it is
practically impossible to have zero dependencies.&lt;/p&gt;
&lt;p&gt;Armin Ronacher
&lt;a href="https://lucumr.pocoo.org/2025/1/24/build-it-yourself/"&gt;called&lt;/a&gt; for a
vibe shift among programmers and I think that this actually exists
already. Everyone I speak to on this topic has agreed that minimizing
dependencies is ideal.&lt;/p&gt;
&lt;p&gt;Rust and JavaScript, with their incredibly minimal standard libraries,
&lt;a href="https://notes.eatonphil.com/2024-03-15-zig-rust-and-other-languages.html#standard-library"&gt;work against this
ideal&lt;/a&gt;. Go, Python, Java, and C# in contrast have
a decent standard library, which helps minimize the explosion of
transitive dependencies.&lt;/p&gt;
&lt;h3 id="examples"&gt;Examples&lt;/h3&gt;&lt;p&gt;I think the standard library should reasonably include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSON, CSV, and Parquet support&lt;/li&gt;
&lt;li&gt;HTTP/2 support (which includes TLS, compression, random number generation, etc.)&lt;/li&gt;
&lt;li&gt;Support for asynchronous IO&lt;/li&gt;
&lt;li&gt;A logging abstraction&lt;/li&gt;
&lt;li&gt;A SQL client abstraction&lt;/li&gt;
&lt;li&gt;Key abstract data types (BTrees, hashmaps, sets, and growable arrays)&lt;/li&gt;
&lt;li&gt;Utilities for working with Unicode, time and timezones&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But I don't think it needs to include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Excel support&lt;/li&gt;
&lt;li&gt;PostgreSQL or Oracle clients&lt;/li&gt;
&lt;li&gt;Flatbuffers support&lt;/li&gt;
&lt;li&gt;Niche data structures&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither of these are intended to be complete lists, just examples.&lt;/p&gt;
&lt;h3 id="walled-gardens"&gt;Walled gardens&lt;/h3&gt;&lt;p&gt;Minimal standard libraries force growing companies to build out their
own internal collection of "standard libraries". As one example,
Bloomberg &lt;a href="https://github.com/bloomberg/bde/wiki"&gt;did this&lt;/a&gt; with
C++. And I've heard of companies doing this already with Rust. This
allows larger companies to manage and minimize the explosion of
transitive dependencies over time.&lt;/p&gt;
&lt;p&gt;All growing companies likely do something like this eventually. But
again, smaller standard libraries incentivize companies to build this
internal standard library earlier on. And the community benefits
relatively little from these internal standard libraries. The
community would benefit more if large organizations contributed back
to an actual standard library.&lt;/p&gt;
&lt;p&gt;Smaller organizations do not have the capacity to build these internal
standard libraries.&lt;/p&gt;
&lt;p&gt;Maybe the situation will lead to libraries like Boost for
JavaScript and Rust programmers. That could be fine.&lt;/p&gt;
&lt;h3 id="versioning"&gt;Versioning&lt;/h3&gt;&lt;p&gt;A comprehensive standard library does not prevent the
language developers from releasing new versions of the standard
library. It is trivial to do this with naming like Go has done
with the &lt;a href="https://go.dev/blog/v2-go-modules"&gt;v2&lt;/a&gt;
pattern. &lt;a href="https://go.dev/blog/randv2"&gt;math/rand/v2&lt;/a&gt; is an example.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;I'm primarily thinking about maintainability, not security. You can
read about the &lt;a href="https://medium.com/@john_25313/c-isnt-a-hangover-rust-isn-t-a-hangover-cure-580c9b35b5ce#:~:text=Rust%20makes%20it,for%20their%20libraries."&gt;security
risks&lt;/a&gt;
of using a language with an ecosystem like Rust from someone who is an
expert on the matter.&lt;/p&gt;
&lt;p&gt;My concern about the standard library does not stop me from using
Rust and JavaScript. They could choose to invest in the standard
library at any time. We have already begun to see
&lt;a href="https://bun.sh/docs/api/s3"&gt;Bun&lt;/a&gt; and &lt;a href="https://jsr.io/@std"&gt;Deno&lt;/a&gt;
to do exactly this. But it is clearly an area
for improvement in Rust and JavaScript. And a mistake for other
languages to avoid repeating.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;While zero dependencies is practically impossible, everyone I've spoken to agrees that minimizing dependencies is ideal. Rust and JavaScript work against this ideal. But they could change at any time. And Bun and Deno are already examples of this.&lt;a href="https://t.co/qkSh6oW1Yd"&gt;https://t.co/qkSh6oW1Yd&lt;/a&gt; &lt;a href="https://t.co/mY1MNErZG7"&gt;pic.twitter.com/mY1MNErZG7&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1883162142888853945?ref_src=twsrc%5Etfw"&gt;January 25, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 25 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-01-25-an-explosion-of-transitive-dependencies.html</guid></item><item><title>Understanding Python re(gex)? book announcement</title><link>https://learnbyexample.github.io/understanding-python-regex-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I just published a new version of my &lt;strong&gt;Understanding Python re(gex)?&lt;/strong&gt; ebook.&lt;/p&gt;
&lt;p&gt;This book will help you learn Python Regular Expressions step-by-step from beginner to advanced levels with &lt;strong&gt;hundreds of examples and exercises&lt;/strong&gt;. The standard library &lt;code&gt;re&lt;/code&gt; as well as the third-party &lt;code&gt;regex&lt;/code&gt; module are covered in this book.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download the PDF/EPUB versions of &lt;strong&gt;Understanding Python re(gex)?&lt;/strong&gt; for FREE till 31-Jan-2025.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/py_regex/c/free"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex/free"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some more amazing offers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All 13 books bundle&lt;/strong&gt; is $18 (normal price $36) — &lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/half_price"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books/HalfPrice"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100 Page Python Intro&lt;/strong&gt; is FREE (normal price $10) — &lt;a href="https://leanpub.com/100pagepythonintro/c/free"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/100pagepythonintro/free"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python version updated to 3.13
&lt;ul&gt;
&lt;li&gt;deprecated features, SyntaxWarning, name change from &lt;code&gt;re.error&lt;/code&gt; to &lt;code&gt;re.PatternError&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Corrected typos, updated descriptions, timing results and external links&lt;/li&gt;
&lt;li&gt;Exercises are now numbered instead of using alphabets&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;Check out my &lt;a href="https://learnbyexample.github.io/tips/"&gt;programming tips&lt;/a&gt; covering Python, command line tools and Vim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos"&gt;Vim tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="re-gex-playground"&gt;re(gex)? playground&lt;a class="zola-anchor" href="#re-gex-playground"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make it easier to experiment, I wrote on an interactive app. See &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexPlayground"&gt;PyRegexPlayground&lt;/a&gt; repo for installation instructions and usage guide. A sample screenshot is shown below:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the Playground screen" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexPlayground/pyregex_finditer.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="re-gex-exercises"&gt;re(gex)? exercises&lt;a class="zola-anchor" href="#re-gex-exercises"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wrote another TUI app to help you solve exercises from this book interactively. See &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PyRegexExercises"&gt;PyRegexExercises&lt;/a&gt; repo for installation steps and &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises/app_guide.md"&gt;app_guide.md&lt;/a&gt; for instructions on using this app. Here's a sample screenshot:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Python regex exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexExercises/pyregex_exercises.png" /&gt;&lt;/p&gt;
&lt;p&gt;See my blog post &lt;a href="https://learnbyexample.github.io/python-regex-cheatsheet/"&gt;Python regex cheatsheet&lt;/a&gt; for a quick reference.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Why is it needed?&lt;/li&gt;
&lt;li&gt;re introduction&lt;/li&gt;
&lt;li&gt;Anchors&lt;/li&gt;
&lt;li&gt;Alternation and Grouping&lt;/li&gt;
&lt;li&gt;Escaping metacharacters&lt;/li&gt;
&lt;li&gt;Dot metacharacter and Quantifiers&lt;/li&gt;
&lt;li&gt;Interlude: Tools for debugging and visualization&lt;/li&gt;
&lt;li&gt;Working with matched portions&lt;/li&gt;
&lt;li&gt;Character class&lt;/li&gt;
&lt;li&gt;Groupings and backreferences&lt;/li&gt;
&lt;li&gt;Interlude: Common tasks&lt;/li&gt;
&lt;li&gt;Lookarounds&lt;/li&gt;
&lt;li&gt;Flags&lt;/li&gt;
&lt;li&gt;Unicode&lt;/li&gt;
&lt;li&gt;regex module&lt;/li&gt;
&lt;li&gt;Gotchas&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can read the book online here: &lt;a href="https://learnbyexample.github.io/py_regular_expressions/"&gt;https://learnbyexample.github.io/py_regular_expressions/&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;https://github.com/learnbyexample/py_regular_expressions&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, rating/review, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/py_regular_expressions/issues"&gt;https://github.com/learnbyexample/py_regular_expressions/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;learn by example.net@gmail.com&lt;/code&gt; (without the spaces)&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 22 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/understanding-python-regex-announcement/</guid></item><item><title>Embedding Python in Rust (for tests)</title><link>http://notes.eatonphil.com/2025-01-22-embedding-python-rust-tests.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/embedding-python-rust-tests"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 22 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-01-22-embedding-python-rust-tests.html</guid></item><item><title>Adventure to the heart of Iceland – The F821 Eyjafjarðarleið</title><link>https://theroadchoseme.com/adventure-to-the-heart-of-iceland-the-f821-eyjafjardarleid</link><description>I&amp;#8217;ve been eager to get up into the heart of Iceland since we arrived, knowing the inland roads won&amp;#8217;t stay snow free forever. To get into the middle of the country we drive the&amp;#46;&amp;#46;&amp;#46;</description><author>The Road Chose Me</author><pubDate>Mon, 20 Jan 2025 21:39:50 GMT</pubDate><guid isPermaLink="true">https://theroadchoseme.com/adventure-to-the-heart-of-iceland-the-f821-eyjafjardarleid</guid></item><item><title>“Fortunately, My Learning Turned Out to be Profitable”</title><link>https://blog.nawaz.org/posts/2025/Jan/fortunately-my-learning-turned-out-to-be-profitable/</link><description>&lt;!--  --&gt;
&lt;blockquote&gt;
&lt;p&gt;Isaac Berman had, by his second wife, Tamara, four children in his
old age. The first was a girl, my mother, Anna Rachel, and then three
sons. Isaac Berman died in 1901, when my mother was six years&amp;nbsp;old.&lt;/p&gt;
&lt;p&gt;Apparently my father did not want to mention things he considered …&lt;/p&gt;&lt;/blockquote&gt;</description><author>Beetle Space</author><pubDate>Mon, 20 Jan 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Jan/fortunately-my-learning-turned-out-to-be-profitable/</guid></item><item><title>An Open Letter to Democratic Candidates and Organizations</title><link>https://blog.rongarret.info/2025/01/an-open-letter-to-democratic-candidates.html</link><description>The ink is barely dry on the (drama-free) certification of the election results and I'm already getting calls from organizations and candidates seeking donations.&amp;nbsp; (Apparently my name is on a lot of lists.)&amp;nbsp; In order to save everyone a lot of time, I thought I'd write up my current thinking so I don't have to have the same conversation dozens of times.First and foremost: I really do</description><author>Rondam Ramblings</author><pubDate>Mon, 20 Jan 2025 09:31:52 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/01/an-open-letter-to-democratic-candidates.html</guid></item><item><title>Home Assistant Voice Preview is an unusable mess.</title><link>https://www.swyx.io/home-assistant-struggles</link><description>&lt;p&gt;I just got a &lt;a href="https://www.home-assistant.io/voice-pe/"&gt;Home Assistant Voice&lt;/a&gt; recently. I was so excited to try it out as a programmable Alexa.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 20 Jan 2025 02:37:56 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/home-assistant-struggles</guid></item><item><title>Logical replication in Postgres: Basics</title><link>http://notes.eatonphil.com/2025-01-17-logical-replication-postgres-basics.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/logical-replication-postgres-basics"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Fri, 17 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2025-01-17-logical-replication-postgres-basics.html</guid></item><item><title>I am (not) a Failure: Lessons Learned From Six (and a half) Failed Startup Attempts</title><link>https://blog.rongarret.info/2025/01/i-am-not-failure-lessons-learned-from.html</link><description>A while back I wrote:I've had many, many failures in my life.&amp;nbsp; (Hm, maybe I should write a blog post about that.)This is that post.&amp;nbsp; I'm writing it not as a lament, but rather because I've ended up in a good place in life despite my extraordinary track record of failing at just about everything I've ever tried.&amp;nbsp; If my younger self had heard these stories he might 
have had a less</description><author>Rondam Ramblings</author><pubDate>Sun, 12 Jan 2025 21:49:48 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2025/01/i-am-not-failure-lessons-learned-from.html</guid></item><item><title>The Unexpected Benefit of Consuming Articles Offline</title><link>https://blog.nawaz.org/posts/2025/Jan/the-unexpected-benefit-of-consuming-articles-offline/</link><description>&lt;p&gt;A while ago, I
&lt;a class="reference external" href="https://blog.nawaz.org/posts/2021/Dec/consuming-articles-offline/"&gt;wrote&lt;/a&gt;
about my shift in reading most articles on a paper as opposed to on a&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;I also
&lt;a class="reference external" href="https://blog.nawaz.org/posts/2024/Apr/reading-articles-via-podcast-software/"&gt;wrote&lt;/a&gt;
about consuming articles via podcast &lt;span class="caps"&gt;SW&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;In the first case, I switched to reading on paper. In the second, I
consumed via audio in my&amp;nbsp;car …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Sun, 12 Jan 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Jan/the-unexpected-benefit-of-consuming-articles-offline/</guid></item><item><title>Simple Go Cache</title><link>https://boyter.org/posts/simple-go-cache/</link><description>&lt;p&gt;Happy new year!&lt;/p&gt;
&lt;p&gt;I am in the middle of converting searchcode.com over to SQLite, which is a far more interesting future post, and as a result had to rewrite some of the core service logic. The previously implemented code had grown over time so I decided to start fresh. Of course the catch of this is introducing regressions, which I did by stripping out some of the caching logic I had implemented.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 12 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/simple-go-cache/</guid></item><item><title>Adding Fediverse Comments to My Static Site</title><link>https://blog.nawaz.org/posts/2025/Jan/adding-fediverse-comments-to-my-static-site/</link><description>&lt;p&gt;The thing one misses most when switching to static site generators is&amp;nbsp;comments.&lt;/p&gt;
&lt;p&gt;You can always use a 3rd party service like
&lt;a class="reference external" href="https://disqus.com/"&gt;Disqus&lt;/a&gt;, but then it becomes a war against&amp;nbsp;spam.&lt;/p&gt;
&lt;p&gt;There are other creative options like creating a subreddit for your
blog, and just moving the discussion there. It …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Fri, 10 Jan 2025 12:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Jan/adding-fediverse-comments-to-my-static-site/</guid></item><item><title>Just Present the First Draft!</title><link>https://blog.nawaz.org/posts/2025/Jan/just-present-the-first-draft/</link><description>&lt;p&gt;You write a working&amp;nbsp;prototype.&lt;/p&gt;
&lt;p&gt;You begin refactoring the&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Your manager says &amp;#8220;Don&amp;#8217;t bother. It&amp;#8217;s working. Why waste time rewriting
when you could be making something new? Any competent programmer should
be able to spend time and understand the&amp;nbsp;code.&amp;#8221;&lt;/p&gt;
&lt;p&gt;You respond with &amp;#8220;Sure. The next time …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Fri, 10 Jan 2025 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2025/Jan/just-present-the-first-draft/</guid></item><item><title>Interactive exercises for GNU grep, sed and awk (TUI apps)</title><link>https://learnbyexample.github.io/interactive-grep-sed-awk-exercises/</link><description>&lt;p&gt;Having an interactive program that automatically loads questions and checks the solution is immensely helpful to have while learning a topic. I've written &lt;a href="https://github.com/learnbyexample/TUI-apps"&gt;TUI apps&lt;/a&gt; with plenty of beginner to intermediate level exercises for &lt;code&gt;GNU grep&lt;/code&gt;, &lt;code&gt;GNU sed&lt;/code&gt; and &lt;code&gt;GNU awk&lt;/code&gt;.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for GNU grep exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/GrepExercises/grep_exercises.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the past few months, I've been using a Python framework called &lt;a href="https://textual.textualize.io/"&gt;Textual&lt;/a&gt; to create interactive TUI apps.&lt;/p&gt;
&lt;p&gt;You'll need Python for this. This app is available on PyPI as &lt;a href="https://pypi.org/project/grepexercises/"&gt;grepexercises&lt;/a&gt;, &lt;a href="https://pypi.org/project/sedexercises/"&gt;sedexercises&lt;/a&gt; and &lt;a href="https://pypi.org/project/awkexercises/"&gt;awkexercises&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install grepexercises sedexercises awkexercises
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app, example shown for the grep command
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; grepexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add aliases to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;grepexercises&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/grepexercises'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# similarly, you can add aliases for the other apps as well
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative to manually managing such virtual environments, you can use &lt;a href="https://github.com/pypa/pipx"&gt;https://github.com/pypa/pipx&lt;/a&gt; instead:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pipx install grepexercises sedexercises awkexercises
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; awkexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As yet another alternative, you can install &lt;code&gt;textual==0.85.2&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone my &lt;a href="https://github.com/learnbyexample/TUI-apps"&gt;TUI-apps repository&lt;/a&gt; and run the Python file from respective folders. For example, &lt;code&gt;grep_exercises.py&lt;/code&gt; for the &lt;code&gt;grep&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; You can use alternative CLI tools to solve these exercises as well. For example, &lt;code&gt;perl&lt;/code&gt; instead of &lt;code&gt;GNU awk&lt;/code&gt; or &lt;code&gt;ripgrep&lt;/code&gt; instead of &lt;code&gt;GNU grep&lt;/code&gt; and so on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="brief-guide"&gt;Brief Guide&lt;a class="zola-anchor" href="#brief-guide"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can either click the buttons using mouse or press the key combinations listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Press &lt;strong&gt;F1&lt;/strong&gt; to view the complete guide from within the app itself.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+p&lt;/strong&gt; and &lt;strong&gt;Ctrl+n&lt;/strong&gt; to navigate the questions list.&lt;/li&gt;
&lt;li&gt;Type the command in the box below the question.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Enter&lt;/strong&gt; to execute the command.
&lt;ul&gt;
&lt;li&gt;Output would be displayed below the command box.&lt;/li&gt;
&lt;li&gt;If the output matches the expected results, the command box will turn &lt;em&gt;green&lt;/em&gt; and reference solutions will also be shown.&lt;/li&gt;
&lt;li&gt;Issues due to errors and timeout (about &lt;code&gt;2&lt;/code&gt; seconds) will be displayed in &lt;em&gt;red&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+s&lt;/strong&gt; to toggle the reference solution box.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+t&lt;/strong&gt; to toggle between light and dark themes.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+q&lt;/strong&gt; to quit the app.&lt;/li&gt;
&lt;li&gt;Some basic readline-like shortcuts are supported, for example &lt;strong&gt;Ctrl+u&lt;/strong&gt;, &lt;strong&gt;Ctrl+k&lt;/strong&gt;, &lt;strong&gt;Ctrl+w&lt;/strong&gt;, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your progress is automatically saved when you close the app and restored when you launch it again later. Already answered questions will be skipped.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; There is no safeguard against the command you are executing. They are treated as if you typed them from a shell session.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="ebooks"&gt;Ebooks&lt;a class="zola-anchor" href="#ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The exercise questions in these apps have been adapted from my programming ebooks: &lt;a href="https://learnbyexample.github.io/books/"&gt;https://learnbyexample.github.io/books/&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd highly appreciate your feedback. Please file &lt;a href="https://github.com/learnbyexample/TUI-apps/issues"&gt;an issue&lt;/a&gt; if there are bugs, crashes, etc.&lt;/p&gt;
&lt;p&gt;Hope you find these TUI apps useful. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/interactive-grep-sed-awk-exercises/</guid></item><item><title>100+ Interactive Python Regex Exercises</title><link>https://learnbyexample.github.io/interactive-python-regex-exercises/</link><description>&lt;p&gt;Having an interactive program that automatically loads questions and checks the solution is wonderful to have while learning a topic. This &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises"&gt;TUI app&lt;/a&gt; has beginner to advanced level exercises for Python regular expressions. There are more than 100 exercises covering both the builtin &lt;code&gt;re&lt;/code&gt; and third-party &lt;code&gt;regex&lt;/code&gt; module.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Python regex exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexExercises/pyregex_exercises.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This app is available on PyPI as &lt;a href="https://pypi.org/project/regexexercises/"&gt;regexexercises&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install regexexercises
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; regexexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you are on Windows, using the &lt;a href="https://en.wikipedia.org/wiki/Windows_Terminal"&gt;Windows Terminal&lt;/a&gt; is recommended. See &lt;a href="https://github.com/learnbyexample/TUI-apps/issues/3#issuecomment-1481488042"&gt;this issue&lt;/a&gt; for Virtual Environment commands and other details.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add this alias to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;regexexercises&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/regexexercises'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative to manually managing such virtual environments, you can use &lt;a href="https://github.com/pypa/pipx"&gt;https://github.com/pypa/pipx&lt;/a&gt; instead:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pipx install regexexercises
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; regexexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As yet another alternative, you can install &lt;code&gt;textual==0.85.2&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone my &lt;a href="https://github.com/learnbyexample/TUI-apps"&gt;TUI-apps&lt;/a&gt; repository and run the &lt;code&gt;pyregex_exercises.py&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="video-demo"&gt;Video demo&lt;a class="zola-anchor" href="#video-demo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="brief-guide"&gt;Brief Guide&lt;a class="zola-anchor" href="#brief-guide"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Type your solution in the input box below the question.
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;ip&lt;/code&gt; variable to represent the sample input.&lt;/li&gt;
&lt;li&gt;Any single valid Python expression will be accepted.&lt;/li&gt;
&lt;li&gt;Some basic readline-like shortcuts are supported, for example &lt;strong&gt;Ctrl+u&lt;/strong&gt;, &lt;strong&gt;Ctrl+k&lt;/strong&gt;, &lt;strong&gt;Ctrl+w&lt;/strong&gt;, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Enter&lt;/strong&gt; to execute the code.
&lt;ul&gt;
&lt;li&gt;Output would be displayed below the command box.&lt;/li&gt;
&lt;li&gt;If the output matches the expected results, the solution box will turn &lt;em&gt;green&lt;/em&gt; and a reference solution will also be shown.&lt;/li&gt;
&lt;li&gt;Error messages due to exceptions will be displayed in &lt;em&gt;red&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+p&lt;/strong&gt; and &lt;strong&gt;Ctrl+n&lt;/strong&gt; to navigate the questions list.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+r&lt;/strong&gt; to toggle between &lt;strong&gt;str&lt;/strong&gt; and &lt;strong&gt;repr&lt;/strong&gt; — helps to spot characters like tabs, newlines, backspaces, etc.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+b&lt;/strong&gt; to toggle between &lt;strong&gt;expected&lt;/strong&gt; and &lt;strong&gt;actual&lt;/strong&gt; — helps to debug incorrect solutions.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+s&lt;/strong&gt; toggle reference solution&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+t&lt;/strong&gt; to toggle between light and dark themes.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+q&lt;/strong&gt; to quit the app.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;F1&lt;/strong&gt; to view a detailed guide within the app itself and press &lt;strong&gt;F2&lt;/strong&gt; to get back to the exercises.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your progress will be automatically saved and restored. Already answered questions will be skipped.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; There is no safeguard against the code you are executing. They are treated as if you executed them from a Python program.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;See &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises/app_guide.md"&gt;app_guide.md&lt;/a&gt; for more detailed instructions.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="ebook"&gt;Ebook&lt;a class="zola-anchor" href="#ebook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See my &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebook to learn regular expressions with hundreds of examples and exercises.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd highly appreciate your feedback. Please file &lt;a href="https://github.com/learnbyexample/TUI-apps/issues"&gt;an issue&lt;/a&gt; if there are bugs, crashes, etc.&lt;/p&gt;
&lt;p&gt;Hope you find this TUI app useful. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/interactive-python-regex-exercises/</guid></item><item><title>Python Regular Expressions Playground</title><link>https://learnbyexample.github.io/python-regex-playground/</link><description>&lt;p&gt;This TUI application is intended as an interactive playground for Python Regular Expressions. The app also includes a comprehensive cheatsheet and several interactive examples.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the Playground screen" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexPlayground/pyregex_finditer.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This app is available on PyPI as &lt;a href="https://pypi.org/project/regexplayground/"&gt;regexplayground&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install regexplayground
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; regexplayground
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add this alias to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;regexplayground&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/regexplayground'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative, you can install &lt;code&gt;textual==0.85.2&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone my &lt;a href="https://github.com/learnbyexample/TUI-apps"&gt;TUI-apps repository&lt;/a&gt; and run the &lt;code&gt;pyregex_playground.py&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines). Here's another screenshot:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the Interactive Examples screen" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PyRegexPlayground/pyregex_examples.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="brief-guide"&gt;Brief Guide&lt;a class="zola-anchor" href="#brief-guide"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can type the search pattern in the &lt;strong&gt;Compile&lt;/strong&gt; input box and press the &lt;strong&gt;Enter&lt;/strong&gt; key (or &lt;strong&gt;Ctrl+r&lt;/strong&gt;) to execute. For example, &lt;code&gt;re.compile(r'\d')&lt;/code&gt; to match digit characters. Matching portions will be highlighted in red.&lt;/p&gt;
&lt;p&gt;The compiled pattern is available via the &lt;code&gt;pat&lt;/code&gt; variable and you can use &lt;code&gt;ip&lt;/code&gt; to refer to the input string. You can transform or extract data by typing appropriate expression in the &lt;strong&gt;Action&lt;/strong&gt; box. For example, &lt;code&gt;pat.sub(r'(\g&amp;lt;0&amp;gt;)', ip)&lt;/code&gt; will add parenthesis around the matching portions.&lt;/p&gt;
&lt;p&gt;You can skip the Compile box and directly use the Action box too. For example, &lt;code&gt;[m.span() for m in re.finditer(r'\d+', ip)]&lt;/code&gt; to get the location of all the matching portions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; There is no safeguard against the commands you have typed. They are treated as if you executed them from a Python program.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Press &lt;strong&gt;F1&lt;/strong&gt; to view the detailed guide from within the app, &lt;strong&gt;F2&lt;/strong&gt; to get back to the Playground from other screens, &lt;strong&gt;F3&lt;/strong&gt; to view a cheatsheet and &lt;strong&gt;F4&lt;/strong&gt; for interactive examples.&lt;/p&gt;
&lt;p&gt;For more detailed instructions, see &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexPlayground/app_guide.md"&gt;app guide&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="ebook"&gt;Ebook&lt;a class="zola-anchor" href="#ebook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See my &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebook to learn regular expressions with hundreds of examples and exercises.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd highly appreciate your feedback. Please file &lt;a href="https://github.com/learnbyexample/TUI-apps/issues"&gt;an issue&lt;/a&gt; if there are bugs, crashes, etc.&lt;/p&gt;
&lt;p&gt;Hope you find this TUI app useful. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/python-regex-playground/</guid></item><item><title>Interactive Linux CLI Text Processing Exercises</title><link>https://learnbyexample.github.io/interactive-linux-cli-exercises/</link><description>&lt;p&gt;Having an interactive program that automatically loads questions and checks the solution is wonderful to have while learning a topic. This &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises"&gt;TUI app&lt;/a&gt; has 60+ beginner to intermediate level exercises for Linux CLI text processing tools.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for CLI exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/CLI-Exercises/cli_exercises.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Last month, I started learning a Python TUI framework called &lt;a href="https://textual.textualize.io/"&gt;Textual&lt;/a&gt;. After working on a &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/SquareTicTacToe"&gt;4x4 board game&lt;/a&gt;, I made an interactive app to help you test your CLI text processing skills with 60+ beginner to intermediate level exercises.&lt;/p&gt;
&lt;p&gt;You'll need Python for this. This app is available on PyPI as &lt;a href="https://pypi.org/project/cliexercises/"&gt;cliexercises&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install cliexercises
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cliexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add this alias to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;cliexercises&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/cliexercises'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative to manually managing such virtual environments, you can use &lt;a href="https://github.com/pypa/pipx"&gt;https://github.com/pypa/pipx&lt;/a&gt; instead:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pipx install cliexercises
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cliexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As yet another alternative, you can install &lt;code&gt;textual==0.85.2&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone my &lt;a href="https://github.com/learnbyexample/TUI-apps"&gt;TUI-apps repository&lt;/a&gt; and run the &lt;code&gt;cli_exercises.py&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines).&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="video-demo"&gt;Video demo&lt;a class="zola-anchor" href="#video-demo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="brief-guide"&gt;Brief Guide&lt;a class="zola-anchor" href="#brief-guide"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+p&lt;/strong&gt; and &lt;strong&gt;Ctrl+n&lt;/strong&gt; to navigate the questions list.&lt;/li&gt;
&lt;li&gt;Type the command in the box below the question.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Enter&lt;/strong&gt; to execute the command.
&lt;ul&gt;
&lt;li&gt;Output would be displayed below the command box.&lt;/li&gt;
&lt;li&gt;If the output matches the expected results, the command box will turn &lt;em&gt;green&lt;/em&gt; and a reference solution will also be shown.&lt;/li&gt;
&lt;li&gt;Issues due to errors and timeout (about &lt;code&gt;2&lt;/code&gt; seconds) will be displayed in &lt;em&gt;red&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+s&lt;/strong&gt; to toggle the reference solution box.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+t&lt;/strong&gt; to toggle between light and dark themes.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+q&lt;/strong&gt; to quit the app.&lt;/li&gt;
&lt;li&gt;Some basic readline-like shortcuts are supported, for example &lt;strong&gt;Ctrl+u&lt;/strong&gt;, &lt;strong&gt;Ctrl+k&lt;/strong&gt;, &lt;strong&gt;Ctrl+w&lt;/strong&gt;, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your progress is automatically saved and restored. Already answered questions will be skipped.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; There is no safeguard against the command you are executing. They are treated as if you typed them from a shell session.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For more detailed instructions, visit &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises"&gt;https://github.com/learnbyexample/TUI-apps/tree/main/CLI-Exercises&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="ebook"&gt;Ebook&lt;a class="zola-anchor" href="#ebook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The exercises in this app have been adapted from my &lt;a href="https://learnbyexample.github.io/books/"&gt;Command Line&lt;/a&gt; ebooks.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd highly appreciate your feedback. Please file &lt;a href="https://github.com/learnbyexample/TUI-apps/issues"&gt;an issue&lt;/a&gt; if there are bugs, crashes, etc.&lt;/p&gt;
&lt;p&gt;Hope you find this TUI app useful. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/interactive-linux-cli-exercises/</guid></item><item><title>Designing a Logo</title><link>https://www.swyx.io/logo-design</link><description>&lt;p&gt;I recently kicked off a 99designs contest for a new logo: https://99designs.com/logo-design/contests/logo-brand-ai-engineering-podcast-help-define-industry-1307842/&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 08 Jan 2025 21:53:58 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/logo-design</guid></item><item><title>My Books to Love &amp;amp; Hate from 2024</title><link>https://caseysoftware.com/blog/my-books-to-love-hate-from-2024?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=my-books-to-love-hate-from-2024</link><description>&lt;p&gt;After college, I resolved to read one book a month. It can be fiction, non-fiction, technical, business-oriented, or whatever as&amp;#160;the&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/my-books-to-love-hate-from-2024"&gt;My Books to Love &amp; Hate from 2024&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Wed, 08 Jan 2025 15:39:00 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/my-books-to-love-hate-from-2024?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=my-books-to-love-hate-from-2024</guid></item><item><title>The Westfijords of Iceland</title><link>https://theroadchoseme.com/the-westfijords-of-iceland</link><description>Iceland is a deceivingly large country, and although we don&amp;#8217;t have a stated goal to drive every road on the island, the Westfijords are a place where that simply makes sense. Around every corner&amp;#46;&amp;#46;&amp;#46;</description><author>The Road Chose Me</author><pubDate>Mon, 06 Jan 2025 17:03:27 GMT</pubDate><guid isPermaLink="true">https://theroadchoseme.com/the-westfijords-of-iceland</guid></item><item><title>Actually Structured Journaling (Jan 2025)</title><link>https://www.swyx.io/structured-journaling-jan-2025</link><description>&lt;p&gt;I was very excited when I saw Cal Newport's episode on &lt;a href="https://www.youtube.com/watch?v=ptcgqoMQ33A"&gt;Structured Journaling&lt;/a&gt; come up, but I was disappointed by the recommendations:&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 06 Jan 2025 12:12:26 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/structured-journaling-jan-2025</guid></item><item><title>Pseudocode for Intentionality</title><link>https://www.swyx.io/intentionality</link><description>&lt;p&gt;I've been interested in being more intentional with my work, time and life recently. Here is &lt;strong&gt;my working definition of how to be more intentional&lt;/strong&gt;.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 06 Jan 2025 11:53:19 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/intentionality</guid></item><item><title>Iceland Aurora Borealis</title><link>https://theroadchoseme.com/iceland-aurora-borealis</link><description>A lot of people ask if the Northern Lights look like they do in videos, or if that is an exaggeration. I can say that YES, they look almost exactly like in the video&amp;#46;&amp;#46;&amp;#46;</description><author>The Road Chose Me</author><pubDate>Fri, 03 Jan 2025 16:50:30 GMT</pubDate><guid isPermaLink="true">https://theroadchoseme.com/iceland-aurora-borealis</guid></item><item><title>Date Me!</title><link>https://www.swyx.io/date-me</link><description>&lt;p&gt;if you like me*, and you're interested but never thought to ask, just ask!&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 02 Jan 2025 07:28:19 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/date-me</guid></item><item><title>Appendix A: The DataProvider Struct</title><link>https://littlegreenviper.com/appendix-a-the-dataprovider-struct/</link><description>The real heart of the demonstration app, is something that we didn&amp;#8217;t cover at all, in the tutorial. That&amp;#8217;s the Data Model for the app, which is in the DataProvider.swift structural type. It&amp;#8217;s a fairly basic type, that consumes some CSV (Comma-Separated-Values) data, and formats it into a configuration that can drive the chart. The ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/appendix-a-the-dataprovider-struct/#more-7199" rel="noopener noreferrer" title="Appendix A: The DataProvider Struct"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Wed, 01 Jan 2025 15:08:14 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/appendix-a-the-dataprovider-struct/</guid></item><item><title>Looking Ahead to 2025</title><link>https://benovermyer.com/blog/2025/01/looking-ahead-to-2025/</link><description>&lt;p&gt;This is going to be a year of big changes.&lt;/p&gt;
&lt;h2 id="goals-for-2025"&gt;Goals for 2025&lt;/h2&gt;
&lt;p&gt;My goals this year will be primarily setting new habits.&lt;/p&gt;
&lt;p&gt;I started guitar lessons in December. I plan to continue practicing
every day, excepting some holidays or travel.&lt;/p&gt;
&lt;p&gt;Also on the creative side, I want to develop a painting habit. This
will probably be less frequent than guitar, but I still want to paint
at least once a week.&lt;/p&gt;
&lt;p&gt;Continuing from last year's success with going alcohol-free, I will
expand this to make Q1 and Q3 entirely alcohol-free. I expect this
will have positive side effects.&lt;/p&gt;
&lt;p&gt;For exercise, I'm getting back on the horse, as it were. I plan on
setting a daily walking habit. I also hope to start jogging again,
and complete at least one 5K.&lt;/p&gt;
&lt;h2 id="thoughts-for-2025"&gt;Thoughts for 2025&lt;/h2&gt;
&lt;p&gt;Politically, the USA is likely to experience some upheaval this year.
I hope nothing catastrophic comes of it. We'll see.&lt;/p&gt;
&lt;p&gt;While it's not a goal, I'd like to pay more attention to finances this
year and end the year with much more savings.&lt;/p&gt;
&lt;p&gt;In 2025 I expect my life to change for the better as I devote myself
to more physical and creative activities, and I'm very much looking
forward to fostering a dog soon.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Wed, 01 Jan 2025 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2025/01/looking-ahead-to-2025/</guid></item><item><title>My 25-year adventure in AI and ML</title><link>https://austinhenley.com/blog/25yearsofai.html</link><description>&lt;a href="https://austinhenley.com/blog/25yearsofai.html"&gt;https://austinhenley.com/blog/25yearsofai.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Wed, 01 Jan 2025 01:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/25yearsofai.html</guid></item></channel></rss>