Finger Portal to WordPress Blog

Finger Primer

The finger protocol, first standardised way back in 1977, is a lightweight directory system for querying resources on a local or remote shared system. Despite barely being used today, it’s so well-established that virtually every modern desktop operating system – Windows, MacOS, Linux etc. – comes with a copy of finger, giving it a similar ubiquity to web browsers! (If you haven’t yet, give it a go.)

If you were using a shared UNIX-like system in the 1970s through 1990s, you might run finger to see who else was logged on at the same time as you, finger chris to get more information about Chris, or finger alice@example.net to look up the details of Alice on the server example.net. Its ability to transcend the boundaries of different systems meant that it was, after a fashion, an example of an early decentralised social network!

I first actively used finger when I was a student at Aberystwyth University. The shared central computers osfa and osfb supported it in what was a pretty typical way: users could add a .plan and/or .project file to their home directory and the contents of these would be output to anybody using finger to look up that user, along with other information like what department they belonged to. I’m simulating from memory so this won’t be remotely accurate, but broadly speaking it looked a little like this –

$ finger dlq9@aber.ac.uk
Login: dlq9                           Name: Dan Q
Directory: /users/9/d/dlq9      Department: Computer Science

Project:
Working on my BEng Software Engineering.

Plan:
    _______
---'   ____)____
          ______)  Finger me!
       _____)
      (____)
---.__(___)

It’s not just about a directory of people, though: you could finger printers to see what their queues were like, finger a time server to ask what time it was, finger a vending machine to see what drinks it had available… even finger for a weather forecast where you are (this one still works as shown below; try it for your own location!) –

$ finger oxford@graph.no
        -= Meteogram for Oxford, Oxfordshire, England, United Kingdom =-
 'C                                                                   Rain (mm)
 12
 11
 10                                                         ^^^=--=--
  9^^^                                                   ===
  8   ^^^===      ======                              ^^^
  7         ======      ===============^^^         =--
  6                                       =--=-----
  5
  4
  3        |  |  |  |  |  |  |                                        1 mm
    17 18 19 20 21 22 23 18/11 02 03 04 05 06 07_08_09_10_11_12_13_14 Hour

     W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W Wind dir.
     6  6  7  7  7  7  7  7  6  6  6  5  5  4  4  4  4  5  6  6  5  5 Wind(m/s)

Legend left axis:   - Sunny   ^ Scattered   = Clouded   =V= Thunder   # Fog
Legend right axis:  | Rain    ! Sleet       * Snow

If you’d just like to play with finger, then finger.farm is a great starting point. They provide free finger hosting and they’re easy to use (try finger dan@finger.farm to find me!). But I had something bigger in mind…

Fingering WordPress

What if you could finger my blog. I.e. if you ran finger blog@danq.me you’d see a summary of some of my recent posts, along with additional addresses you could finger to read the full content of each. This could be the world’s first finger-to-WordPress gateway; y’know, for if you thought the world needed such a thing. Here’s how I did it:

  1. Installed efingerd; I’m using the Debian binaries.
  2. Opened a hole in the firewall on port 79 so the outside world could access it (ufw allow 1965; utf reload).
  3. The default configuration for efingerd acts like a “typical” finger server, but it’s highly programmable to make it “smarter”. I:
    1. Blanked /etc/efingerd/list to prevent any output from “listing” the server (finger @danq.me).
    2. Replaced the contents of /etc/efingerd/list and /etc/efingerd/nouser(which are run when a request matches, or doesn’t match, a user account name) with a call to my script: /usr/local/bin/finger-to-wordpress "$3". $3 holds the username that was requested, so we can act on it.
    3. Created /usr/local/bin/finger-to-wordpressa Ruby program that either (a) lists a selection of posts or (b) returns a specific post (stripping the HTML tags)

Screenshot showing this blog rendered as plain text as the result of running finger blog@danq.me at a Linux terminal.

In future, I might use some extra tags or metadata to enhance finger-friendly WordPress posts. The infrastructure’s in place already (I already have tags that I use to make certain kinds of content available only via certain media – shh!). You might rightly as what the point is of this entire enterprise, of course, and you’d be well within your rights to ask such a question. But I think the best answer available is “because Dan”.

Screenshot showing this blog post rendered as plain text as the result of running finger wp-finger@danq.me at a Linux terminal.

If you want to see my blog in a whole new way, give it a go: run finger blog@danq.me on your computer and follow the instructions.

× ×

Counselling and Constellations

Over the last three or four years I’ve undertaken a couple of different rounds of psychotherapy. I liken the experience to that of spotting constellations in the night sky.

A deep red sky over the silhouette of a treeline, with stars beginning to appear. Six crisscrossing straight lines each connect three to five stars in a row, giving the illusion that their location is not random.
If you’ve got enough arbitrary points, of course, you can draw whatever lines you want. But that’s not the metaphor I’m going for.

That’s probably the result of the goal I stated when going in to the first round: I’d like you to help while I take myself apart, try to understand how I work, and then put myself back together again.1 I’m trying to connect the dots between who-I-once-was and who-I-am-now and find causal influences.

As I’m sure you can imagine: with an opening statement like that I needed to contact a few different therapists before I found one who was compatible with my aims2. But then, I was always taught to get three quotes before hiring a professional.

Two abstract constellations of stars drawn onto a picture of stars over the hills as the last of the sunlight begins to vanish.
Constellations are necessarily subjective. It’s always pleased me to think about how Orion the Hunter, one of the Northern hemisphere’s most-recognisable Winter visitors, was  interpreted by the Lakota people to represent a bison, and some Indian traditions see it as a deer.

It’s that “connecting the dots” that feels like constellation-spotting. A lot of the counselling work (and the “homework” that came afterwards) has stemmed from ideas like:

  • This star represents a moment in my past.
  • This star represents a facet of my identity today.
  • If we draw a line from one to the other, what does the resulting constellation look like?

I suppose that what I’ve been doing is using the lens of retrospection to ask: “Hey, why am I like this? Is this part of it? And what impact did that have on me? Why can’t I see it?”

When you’re stargazing, sometimes you have to ask somebody to point out the shape in front of you before you can see it for yourself.

A silhouette of a person sits on a rock, gazing up at an incredible number of stars in an inky black sky.
A better writer would make an allusion to looking into one’s past through the symbolism of looking into the Universe’s past, but I’m not that writer.

I haven’t yet finished this self-analytical journey, but I’m in an extended “homework” phase where I’m finding my own way: joining the dots for myself. Once somebody’s helped you find those constellations that mean something to you, it’s easier to pick them out when you stargaze alone.

Footnotes

1 To nobody’s surprise whatsoever, I can reveal that ever since I was a child I’ve enjoyed taking things apart to understand how they work. I wasn’t always so good at putting them back together again, though. My first alarm clock died that way, as did countless small clockwork and electronic toys.

2 I also used my introductory contact to lay out my counselling qualifications, in case they were a barrier for a potential therapist, but it turns out this wasn’t as much of a barrier as the fact that I arrived with a concrete mandate.

× × ×

Wake Me Up When September Ends

Mastodon’s Eternal September begins

In the light of the so-called “Twitter migration”, I’ve spent a lot of the last week helping people new to Mastodon/the Fediverse in general to understand it. Or at least, to understand how it’s different from Twitter.1

If you’re among those jumping ship, by the way, can I recommend that you do two things:

  1. Don’t stop after reading an article about what Mastodon is and how it works (start here!); please also read about the established etiquette, and
  2. Don’t come in with the expectation that it’s “like Twitter but…”, because the ways it’s not like Twitter are more-important (and nobody wants it to be more like Twitter).

The experience has filled me with feelings, for which I really appreciated that Hugh Rundle found such great words to share, comparing the surge of new users to September(ish) 1993:

The tools, protocols and culture of the fediverse were built by trans and queer feminists. Those people had already started to feel sidelined from their own project when people like me started turning up a few year ago. This isn’t the first time fediverse users have had to deal with a significant state change and feeling of loss. Nevertheless, the basic principles have mostly held up to now: the culture and technical systems were deliberately designed on principles of consent, agency, and community safety.

If the people who built the fediverse generally sought to protect users, corporate platforms like Twitter seek to control their users… [Academics and advertisers] can claim that legally Twitter has the right to do whatever it wants with this data, and ethically users gave permission for this data to be used in any way when they ticked “I agree” to the Terms of Service.

This attitude has moved with the new influx. Loudly proclaiming that content warnings are censorship, that functionality that has been deliberately unimplemented due to community safety concerns are “missing” or “broken”, and that volunteer-run servers maintaining control over who they allow and under what conditions are “exclusionary”. No consideration is given to why the norms and affordances of Mastodon and the broader fediverse exist, and whether the actor they are designed to protect against might be you.

I’d highly recommend you read the whole thing because it’s excellent.

Photo showing fragile dewey spiderwebs on blades of grass.

Part of the solution

I genuinely believe that the fediverse is among our best bets for making a break from the silos of the corporate Web, and to do that it has to scale – it’s only the speed at which it’s being asked to do so that’s problematic.

Aside from what I’m already doing – trying to tutor (tootor?) new fediversians about how to integrate in an appropriate and respectful manner and doing a little to supporting the expansion of the software that makes it tick… I wonder what more I could/should be doing.

Would my effort be best-spent be running a server (one not-just-for-me, I mean: abnib.social, anyone?), or should I use that time and money to support existing instances directly? Should I brush up on my ActivityPub spec so I can be a more-useful developer, or am I better-placed to focus on tending my own digital garden first? Or maybe I’m looking at it all wrong and I should be trying to dissuade people from piling-on to a system that might well not be right for them (nor they for it!)?

I don’t know the answers to these questions, but I’m hoping to work them out soon.

Addendum

It only occurred to me after the fact that I should mention that you can find me at @dan@danq.me.

Footnotes

1 Important: I’m no expert. I’ve been doing fediverse things for about 3 years but I’m relatively quiet on Mastodon. Also, I’ve never really understood or gotten along with Twitter, so I’m even less an expert on that. Don’t assume that I’m an authority on anything at all, and especially not social media.

×

Bisect your Priority of Constituencies

Your product, service, or organisation almost certainly has a priority of constituencies, even if it’s not written down or otherwise formally-encoded. A famous example would be that expressed in the Web Platform Design Principles. It dictates how you decide between two competing needs, all other things being equal.

At Three Rings, for example, our priority of constituencies might1 look like this:

  1. The needs of volunteers are more important than
  2. The needs of voluntary organisations, which are more important than
  3. Continuation of the Three Rings service, which is more important than
  4. Adherance to technical standards and best practice, which is more important than
  5. Development of new features

These are all things we care about, but we’re talking about where we might choose to rank them, relative to one another.

Semicircular illustration showing five facets of growing relative importance. From least to most, they are: new features, standards-compliance, service continuity, organisation needs, volunteer needs.
The priorities and constituencies portrayed in this illustration are ficticious. Any resemblence to real priorities and constituencies, whether living or dead, is entirely coincidental.

The priorities of an organisation you’re involved with won’t be the same: perhaps it includes shareholders, regulatory compliance, different kinds of end-users, employees, profits, different measures of social good, or various measurable outputs. That’s fine: every system is different.

But what I’d challenge you to do is find ways to bisect your priorities. Invent scenarios that pit each constituency against itself another and discuss how they should be prioritised, all other things being equal.

Using the example above, I might ask “which is more important?” in each category:

  1. The needs of the volunteers developing Three Rings, or the needs of the volunteers who use it?
  2. The needs of organisations that currently use the system, or the needs of organisations that are considering using it?
  3. Achieving a high level of uptime, or promptly installing system updates?
  4. Compliance with standards as-written, or maximum compatibility with devices as-used?
  5. Implementation of new features that are the most popular user requests, or those which provide the biggest impact-to-effort payoff?
Illustrated priority list from above, which each item split into two and re-named such that they are, from least to most important: popular features, impact features, compliance, usability, uptime, updates, future clients, current clients, other volunteers, our volunteers.
These might not be your answers to the same questions. They’re not even necessarily mine, and they’re even less-likely to be representative of Three Rings CIC. It’s just illustrative.

The aim of the exercise isn’t to come up with a set of commandments for your company. If you come up with something you can codify, that’s great, but if you and your stakeholders just use it as an exercise in understanding the relative importance of different goals, that’s great too. Finding where people disagree is more-important than having a unifying creed2.

And of course this exercise applicable to more than just organisational priorities. Use it for projects or standards. Use it for systems where you’re the only participant, as a thought exercise. A priority of constituencies can be a beautiful thing, but you can understand it better if you’re willing to take it apart once in a while. Bisect your priorities, and see what you find.

Footnotes

1 Three Rings doesn’t have an explicit priority of constituencies: the example I give is based on my own interpretation, but I’m only a small part of the organisation.

2 Having a creed is awesome too, though, as I’ve said before.

× ×

My First MP3

Duration

Podcast Version

This post is also available as a podcast. Listen here, download for later, or subscribe wherever you consume podcasts.

Somebody shared with me a tweet about the tragedy of being a Gen X’er and having to buy all your music again and again as formats evolve. Somebody else shared with me Kyla La Grange‘s cover of a particular song .Together… these reminded me that I’ve never told you the story of my first MP31

Screenshot of tweet by @bewgtweets posted Oct 17, 2021, reading: If you want to know why Gen X’ers are always mad it’s because we had to replace our record collections with a tape collection that was then replaced with a cd collection that was then replaced with MP3’s and damn it how many time must I pay to listen to grunge
I didn’t/don’t own much vinyl – perhaps mostly because I had a tape deck in my bedroom years before a record player – but I’ve felt this pain. And don’t get me started on the videogames I’ve paid for multiple times.

In the Summer of 1995 I bought the CD single of the (still excellent!) Set You Free by N-Trance.2 I’d heard about this new-fangled “MP3” audio format, so soon afterwards I decided to rip a copy of the song to my PC.

I was using a 66MHz 486SX CPU, and without an embedded FPU I didn’t quite have the spare processing power to rip-and-encode in a single pass.3 So instead I first ripped to an uncompressed PCM .wav file and then performed the encoding: the former step was done almost in real-time (I listened to the track as it ripped!), about 7 minutes. The latter step took about 20 minutes.

So… about half an hour in total, to rip a single song.

Dan, as a teenager, sits at a desk with his hand to his chin. In the foreground, a beiege two-button wired ball-type computer mouse rests on the corner of the desk. Dan is wearing a black t-shirt with a red devil face printed onto it.
Progress bar, you say? I’ll just sit here and wait then, I guess. Actual contemporary-ish photo.

Creating a (what would now be considered an apalling) 32kHz mono-channel file, this meant that I briefly stored both a 27MB wave file and the final ~4MB MP3 file. 31MB might not sound huge, but I only had a total of 145MB of hard drive space at the time, so 31MB consumed over a fifth of my entire fixed storage! Even after deleting the intermediary wave file I was left with a single song consuming around 3% of my space, which is mind-boggling to think about in hindsight.

But it felt like magic. I called my friend Gary to tell him about it. “This is going to be massive!” I said. At the time, I meant for techy people: I could imagine a future in which, with more hard drive space, I’d keep all my music this way… or else bundle entire artists onto writable CDs in this new format, making albums obsolete. I never considered that over the coming decade or so the format would enter the public consciousness, let alone that it’d take off like it did.

A young man in jeans and a blue coat stands on the patio in the back garden of a terraced house, dropping a half-brick onto the floor. In the background, an unused rabbit hutch and a dustbin can be seen. The photo is clearly taken using a flash, at night.
If you’re thinking of Gary and I as the kind of reprobates who helped bring on the golden age of music piracy… I’d like to distract you with a bigger show of yobbish behaviour in the form of this photo from the day we played at dropping half-bricks onto starter pistol ammunition.

The MP3 file I produced had a fault. Most of the way through the encoding process, I got bored and ran another program, and this must’ve interfered with the stream because there was an audible “blip” noise about 30 seconds from the end of the track. You’d have to be listening carefully to hear it, or else know what you were looking for, but it was there. I didn’t want to go through the whole process again, so I left it.

But that artefact uniquely identified that copy of what was, in the end, a popular song to have in your digital music collection. As the years went by and I traded MP3 files in bulk at LAN parties or on CD-Rs or, on at least one ocassion, on an Iomega Zip disk (remember those?), I’d ocassionally see N-Trance - (Only Love Can) Set You Free.mp34 being passed around and play it, to see if it was “my” copy.

Sometimes the ID3 tags had been changed because for example the previous owner had decided it deserved to be considered Genre: Dance instead of Genre: Trance5. But I could still identify that file because of the audio fingerprint, distinct to the first MP3 I ever created.

I still had that file when I went to university (where it occupied a smaller proportion of my hard drive space) and hearing that distinctive “blip” would remind me about the ordeal that was involved in its creation. I don’t have it any more, but perhaps somebody else still does.

Footnotes

1 I might never have told this story on my blog, but eagle-eyed readers may remember that I’ve certainly hinted at it before now.

2 Rewatching that music video, I’m struck by a recollection of how crazy popular crossfades were on 1990s dance music videos. More than just a transition, I’m pretty sure that most of the frames of that video are mid-crossfade: it feels like I’m watching Kelly Llorenna hanging out of a sunroof but I accidentally left one of my eyeballs in a smoky nightclub and can still see out of it as well.

3 I initially tried to convert directly from red book format to an MP3 file, but the encoding process was too slow and the CD drive’s buffer filled up and didn’t get drained by the processor, which was still presumably bogged down with framing or fourier-transforming earlier parts of the track. The CD drive reasonably assumed that it wasn’t actually being used and spun-down the drive motor, and this caused it to lose its place in the track, killing the whole process and leaving me with about a 40 second recording.

4 Yes, that filename isn’t quite the correct title. I was wrong.

5 No, it’s clearly trance. They were wrong.

× ×

XPath Scraping with FreshRSS

I’ve been spending a while running on reduced brain capacity lately so, to ease myself back into thinking like a programmer, I upgraded my preferred feed reader FreshRSS to version 1.20.0 – which was released a couple of weeks ago – and tried out what I believe is its killer new feature: HTML + XPath scraping.

Screenshot showing Beverley Newing's weblog; two articles are visible - Paperback copy of 'Disability Visibility', edited by Alice Wong, next to a cup of tea Setting up an Accessibility Book Club, published on 1 March 2022, and Reflecting on 2021, published on 1 January 2022.
I like to keep up-to-date with my friend Bev’s blog, but they don’t have an RSS feed.

I’ve been using RSS1 for about 20 years and I love it. It feels great to be able to curate my updates based on “what I care about”, and not on “what some social network thinks I should care about”, to keep things to read later, to prioritise effectively based on my own categorisation, to consume content offline and have my to-read list synchronise later, etc.

RSS never went away, of course (what do you think a podcast is?), but it got steamrollered out of the public eye by big companies who make their money out of keeping your eyes on their platforms and off the open Web. But it feels like it’s slowly coming back: even Substack – whose entire thing is that an email client is more-convenient than a feed reader for most people – launched an RSS reader this week!

A smartphone on a wooden surface. The screen shows the FeedMe app, showing the most-recent blog post from Beverley's blog.
My day usually starts in my feed reader, accessed via the FeedMe app from my mobile (although FreshRSS provides a reasonably good responsive interface out-of-the-box!)

I love RSS so much that I routinely retrofit other people’s websites with feeds just so I can subscribe to them: I even published the tool I use to do so! Whether filtering sports headlines out of BBC News, turning retro webcomics into “reading lists” so I can track my progress, or just working around sites that really should have feeds but refuse to, I just love sidestepping these “missing feeds”. My friend Beverley has a blog without any kind of feed, so I added one so I could subscribe to it. Magic.

But with FreshRSS 1.20.0, I no longer have to maintain my own tool to get this brilliant functionality, and I’m overjoyed. Let’s look at how it works by re-subscribing to Beverley’s blog but without a middleware tool.

Screenshot showing FetchRSS being used to graphically create a feed from Beverley's blog.
This post is about to get pretty technical. If you don’t want to learn some XPath but just want to make a feed out of a web page, use a graphical tool like FetchRSS.

In the latest version of FreshRSS, when you add a new feed to your reader, a new section “Type of feed source” is available. Unfold it, and you can change from the default (“RSS / Atom”) to the new option “HTML + XPath (Web scraping)”. Put a human-readable page address rather than a feed address into the “Feed URL” field and fill these fields to tell FreshRSS how to parse the page to get the content you want. Note that it doesn’t matter if the web page isn’t valid XML (e.g. missing closing tags) because it’s going to get run through PHP’s DOMDocument anyway which will “correct” for some really sloppy code if needed.

Browser debugger running document.evaluate('//li[@class="blog__post-preview"]', document).iterateNext() on Beverley's weblog and getting the first blog entry.
You can use your browser’s debugger to help check your XPath rules: here I’ve run  document.evaluate('//li[@class="blog__post-preview"]', document).iterateNext() and got back the first blog post on the page, so I know I’m on the right track.
You’ll need to use XPath to express how to find a “feed item” on the page. Here’s the rules I used for https://webdevbev.co.uk/blog.html (many of these fields were optional – I didn’t have to do this much work):
  • Feed title: //h1
    I override this anyway in FreshRSS, so I could just have used the a string, but I wanted the XPath practice. There’s only one <h1> on the page, and it can be considered the “title” of the feed.
  • Finding items: //li[@class="blog__post-preview"]
    Each “post” on the page is an <li class="blog__post-preview">.
  • Item titles: descendant::h2
    Each post has a <h2> which is the post title. The descendant:: selector scopes the search to each post as found above.
  • Item content: descendant::p[3]
    Beverley’s static site generator template puts the post summary in the third paragraph of the <li>, which we can select like this.
  • Item link: descendant::h2/a/@href
    This expects a URL, so we need the /@href to make sure we get the value of the <h2><a href="...">, rather than its contents.
  • Item thumbnail: descendant::img[@class="blog__image--preview"]/@src
    Again, this expects a URL, which we get from the <img src="...">.
  • Item author: "Beverley Newing"
    Beverley’s blog doesn’t host any guest posts, so I just use a string literal here.
  • Item date: substring-after(descendant::p[@class="blog__date-posted"], "Date posted: ")
    This is the only complicated one: the published dates on Beverley’s blog aren’t explicitly marked-up, but part of a string that begins with the words “Date posted: “, so I use XPath’s substring-after function to strtip this. The result gets passed to PHP’s strtotime(), which is pretty tolerant of different date formats (although not of the words “Date posted:” it turns out!).
Screenshot: Adding a "HTML + XPath (Web scraping)" feed via FreshRSS.
I’d love one day for FreshRSS to provide some kind of “preview” feature here so you can see what you’ll expect to get back, as you work. That, and support for different input types (JSON, perhaps?), perhaps other selectors (I find CSS-style selectors much simpler than XPath), and maybe even an option to execute Javascript on the page before scraping (I use this in my own toolchain, but that’s just because I want to have my cake and eat it too). But this is still all pretty awesome.

I hope that this is just the beginning for this new killer feature in FreshRSS: there’s so much more it can be and do. But for now, I’m still mighty impressed that I can begin to phase-out my use of my relatively resource-intensive feed-building middleware and use my feed reader to do more and more of the heavy lifting for which I love it so much.

I also love that this functionally adds h-feed support in by the back door. I’d still prefer there to be a “h-feed” option in the “Type of feed source” drop-down, but at least I can add such support manually, now!

Beverley's blog post "Setting up an Accessibility Book Club" in FreshRSS.
The finished result: Bev’s blog posts appear directly in my feed reader, even though they don’t have a feed, and now without going through the middleware I’d set up for that purpose.

Footnotes

1 When I say RSS, I mean feed. Most of the feeds I subscribe to are RSS feeds, but some are Atom feeds, h-feed, etc. But I can’t get over the old-fashioned name, and I don’t care to try.

× × × × × ×

Covid Brain

I managed to dodge infection for 922 days of the Covid pandemic1, but it caught up with me eventually.

Lateral flow test, with "DAN" written on it, showing a solid control line and a very clear solid test line: a clear positive result.
Well, shit.

Frankly, it’s surprising that it took this long. We’ve always been careful, in accordance with guidance at any given time, nd we all got our jabs and boosters as soon as we were able… but conversely: we’ve got school-age children who naturally seem to be the biggest disease vectors imaginable. Our youngest, in fact, already had Covid, but the rest of us managed to dodge it perhaps thanks to all these precautions.

Vials of Covid vaccine scattered across a red background. Photo courtesy Maksim Goncharenok.
The vaccine provide protection, but it’s not a magical force-field.

Luckily I’m not suffering too badly, probably thanks to the immunisation. It’s still not great, but I dread to think how it might have been without the benefit of the jab! A minor fever came and went, and then it’s just been a few days of coughing, exhaustion, and… the most-incredible level of brain-fog.

Dan with the dog, in the garden.
Today, for example, I completey blanked the word “toilet” and struggled for some time to express to the dog why I’d brought her into the garden, while she stared at me expectantly.

I’ve taken the week off work to recover, which was a wise choice. As well as getting rest, it’s meant that I’ve managed to avoid writing production code with my addled brain! Instead, I’ve spent a lot of time chilling in bed and watching all of the films that I’d been meaning to! This week, I’ve watched:

  • Peggy Sue Got Married (y’know, that other mid-1980s movie about time travel and being a teenager in the 1950s). It was okay; some bits of the direction were spectacular for its age, like the “through the mirror” filming.
  • Fall. I enjoyed this more than I expected to. It’s not great, but while I spent most of the time complaining about the lack of believability in the setting and the characters’ reactions, the acting was good and the tension “worked”: it was ocassionally pretty vertigo-inducing, and that’s not just because I’ve been having some Covid-related dizziness!
  • RRR. Oh my god this Tollywood action spectacle was an adventure. At one point it’s a bromantic buddy comedy, then later there’s a dance-off, then for a while there’s a wonderful “even language can’t divide us” romance, but then later a man picks up a motorcycle with one hand and uses it to beat up an entire army, and somehow it all feels like it belongs together. The symbolism’s so thick you can spread it (tl;dr: colonialism bad), but it’s still a riot of a film.
  • Cyrano, which I feel was under-rated but that could just be that I have a soft spot for the story… and a love of musical theatre.
  • Also, at times when I didn’t think my brain had the focus for something new, I re-watched Dude, Where’s My Car? because I figured a stoner comedy that re-replains the plot every 20 minutes or so was about as good as I could expect my brain to handle at the time, and Everything Everywhere All At Once which I’ve now seen three times and loved every single one: it’s one of my favourite films.
Dan lying in bed, giving a weak "thumbs up".
See, I’m fine! (Feel like I’ve spent a lot of time lying here, this week.)

Anyway: hopefully next week I’ll be feeling more normal and my poor Covid-struck brain can be trusted with code again. Until then: time to try to rest some more.

Footnotes

1 Based on the World Health Organisation’s declaration of the outbreak being a pandemic on 11 March 2020 and my positive test on 19 September 2022, I stayed uninfected for two years, six months, one week, and one day. But who’s counting?

× × × ×

.well-known/links in WordPress

Via Jeremy Keith I today discovered Jim Nielsen‘s suggestion for a website’s /.well-known/links to be a place where it can host a JSON-formatted list of all of its outgoing links.

That’s a really useful thing to have in this new age of the web, where Refererer: headers are no-longer commonly passed cross-domain and Google Search no longer provides the link: operator. If you want to know if I’ve ever linked to your site, it’s a bit of a drag to find out.

JSON output from DanQ.me's .well-known/links, showing 1,150 outbound links to en.wikipedia.org domains, for topics like "Bulls and Cows", "Shebang (Unix)", "Imposter syndrome", "Interrobang", and "Blakedown".
To nobody’s surprise whatsoever, I’ve made a so many links to Wikipedia that I might be single-handedly responsible for their PageRank.

So, obviously, I’ve written an implementation for WordPress. It’s really basic right now, but the source code can be found here if you want it. Install it as a plugin and run wp outbound-links to kick it off. It’s fast: it takes 3-5 seconds to parse the entirety of danq.me, and I’ve got somewhere in the region of 5,000 posts to parse.

You can see the results at https://danq.me/.well-known/links – if you’ve ever wondered “has Dan ever linked to my site?”, now you can find the answer.

If this could be useful to you, let’s collaborate on making this into an actually-useful plugin! Otherwise it’ll just languish “as-is”, which is good enough for my purposes.

Beating Children at Mastermind

This post is also available as a video. If you'd prefer to watch/listen to me talk about this topic, give it a look.

This blog post is also available as a video. Would you prefer to watch/listen to me tell you about how I’ve implemented a tool to help me beat the kids when we play Mastermind?

I swear that I used to be good at Mastermind when I was a kid. But now, when it’s my turn to break the code that one of our kids has chosen, I fail more often than I succeed. That’s no good!

Black, white, brown, blue, green, orange and yellow Mastermind pegs in a disordered heap.
If you didn’t have me pegged as a board gamer… where the hell have you been?

Mastermind and me

Maybe it’s because I’m distracted; multitasking doesn’t help problem-solving. Or it’s because we’re “Super” Mastermind, which differs from the one I had as a child in that eight (not six) peg colours are available and secret codes are permitted to have duplicate peg colours. These changes increase the possible permutations from 360 to 4,096, but the number of guesses allowed only goes up from 8 to 10. That’s hard.

A plastic Mastermind board in brown and green; it has twelve spots for guessing and shows six coloured pegs. The game has been won on the sixth guess.
The set I had as a kid was like this, I think. Photo courtesy ZeroOne; CC-BY-SA license.

Or maybe it’s just that I’ve gotten lazy and I’m now more-likely to try to “solve” a puzzle using a computer to try to crack a code using my brain alone. See for example my efforts to determine the hardest hangman words and make an adverserial hangman game, to generate solvable puzzles for my lock puzzle game, to cheat at online jigsaws, or to balance my D&D-themed Wordle clone.

Hey, that’s an idea. Let’s crack the code… by writing some code!

Screenshot showing Mastermind game from WebGamesOnline.com. Seven guesses have been made, each using only one colour for each of the four pegs, and no guesses are corect; only red pegs have never been guessed.
This online edition plays a lot like the version our kids play, although the peg colours are different. Next guess should be an easy solve!

Representing a search space

The search space for Super Mastermind isn’t enormous, and it lends itself to some highly-efficient computerised storage.

There are 8 different colours of peg. We can express these colours as a number between 0 and 7, in three bits of binary, like this:

Decimal Binary Colour
0 000 Red
1 001 Orange
2 010 Yellow
3 011 Green
4 100 Blue
5 101 Pink
6 110 Purple
7 111 White

There are four pegs in a row, so we can express any given combination of coloured pegs as a 12-bit binary number. E.g. 100 110 111 010 would represent the permutation blue (100), purple (110), white (111), yellow (010). The total search space, therefore, is the range of numbers from 000000000000 through 111111111111… that is: decimal 0 through 4,095:

Decimal Binary Colours
0 000000000000 Red, red, red, red
1 000000000001 Red, red, red, orange
2 000000000010 Red, red, red, yellow
…………
4092 111111111100 White, white, white, blue
4093 111111111101 White, white, white, pink
4094 111111111110 White, white, white, purple
4095 111111111111 White, white, white, white

Whenever we make a guess, we get feedback in the form of two variables: each peg that is in the right place is a bull; each that represents a peg in the secret code but isn’t in the right place is a cow (the names come from Mastermind’s precursor, Bulls & Cows). Four bulls would be an immediate win (lucky!), any other combination of bulls and cows is still valuable information. Even a zero-score guess is valuable- potentially very valuable! – because it tells the player that none of the pegs they’ve guessed appear in the secret code.

A plastic Mastermind board in blue and yellow with ten guess spaces and eight pegs. The sixth guess is unscored but looks likely to be the valid solution.
If one of Wordle‘s parents was Scrabble, then this was the other. Just ask its Auntie Twitter.

Solving with Javascript

The latest versions of Javascript support binary literals and bitwise operations, so we can encode and decode between arrays of four coloured pegs (numbers 0-7) and the number 0-4,095 representing the guess as shown below. Decoding uses an AND bitmask to filter to the requisite digits then divides by the order of magnitude. Encoding is just a reduce function that bitshift-concatenates the numbers together.

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
 * Decode a candidate into four peg values by using binary bitwise operations.
 */
function decodeCandidate(candidate){
  return [
    (candidate & 0b111000000000) / 0b001000000000,
    (candidate & 0b000111000000) / 0b000001000000,
    (candidate & 0b000000111000) / 0b000000001000,
    (candidate & 0b000000000111) / 0b000000000001
  ];
}

/**
 * Given an array of four integers (0-7) to represent the pegs, in order, returns a single-number
 * candidate representation.
 */
function encodeCandidate(pegs) {
  return pegs.reduce((a, b)=>(a << 3) + b);
}

With this, we can simply:

  1. Produce a list of candidate solutions (an array containing numbers 0 through 4,095).
  2. Choose one candidate, use it as a guess, and ask the code-maker how it scores.
  3. Eliminate from the candidate solutions list all solutions that would not score the same number of bulls and cows for the guess that was made.
  4. Repeat from step #2 until you win.

Step 3’s the most important one there. Given a function getScore( solution, guess ) which returns an array of [ bulls, cows ] a given guess would score if faced with a specific solution, that code would look like this (I’m convined there must be a more-performant way to eliminate candidates from the list with XOR bitmasks, but I haven’t worked out what it is yet):

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/**
 * Given a guess (array of four integers from 0-7 to represent the pegs, in order) and the number
 * of bulls (number of pegs in the guess that are in the right place) and cows (number of pegs in the
 * guess that are correct but in the wrong place), eliminates from the candidates array all guesses
 * invalidated by this result. Return true if successful, false otherwise.
 */
function eliminateCandidates(guess, bulls, cows){
  const newCandidatesList = data.candidates.filter(candidate=>{
    const score = getScore(candidate, guess);
    return (score[0] == bulls) && (score[1] == cows);
  });
  if(newCandidatesList.length == 0) {
    alert('That response would reduce the candidate list to zero.');
    return false;
  }
  data.candidates = newCandidatesList;
  chooseNextGuess();
  return true;
}

I continued in this fashion to write a full solution (source code). It uses ReefJS for component rendering and state management, and you can try it for yourself right in your web browser. If you play against the online version I mentioned you’ll need to transpose the colours in your head: the physical version I play with the kids has pink and purple pegs, but the online one replaces these with brown and black.

Testing the solution

Let’s try it out against the online version:

As expected, my code works well-enough to win the game every time I’ve tried, both against computerised and in-person opponents. So – unless you’ve been actively thinking about the specifics of the algorithm I’ve employed – it might surprise you to discover that… my solution is very-much a suboptimal one!

A young boy sits cross-legged on the floor, grinning excitedly at a Mastermind board (from the code-maker's side).
My code has only failed to win a single game… and that turned out to because my opponent, playing overexcitedly, cheated in the third turn. To be fair, my code didn’t lose either, though: it identified that a mistake must have been made and we declared the round void when we identified the problem.

My solution is suboptimal

A couple of games in, the suboptimality of my solution became pretty visible. Sure, it still won every game, but it was a blunt instrument, and anybody who’s seriously thought about games like this can tell you why. You know how when you play e.g. Wordle (but not in “hard mode”) you sometimes want to type in a word that can’t possibly be the solution because it’s the best way to rule in (or out) certain key letters? This kind of strategic search space bisection reduces the mean number of guesses you need to solve the puzzle, and the same’s true in Mastermind. But because my solver will only propose guesses from the list of candidate solutions, it can’t make this kind of improvement.

Animation showing how three clues alone are sufficient to derive a unique answer from the search space of the original "break into us" lock puzzle.
My blog post about Break Into Us used a series of visual metaphors to show search space dissection, including this one. If you missed it, it might be worth reading.

Search space bisection is also used in my adverserial hangman game, but in this case the aim is to split the search space in such a way that no matter what guess a player makes, they always find themselves in the larger remaining portion of the search space, to maximise the number of guesses they have to make. Y’know, because it’s evil.

Screenshot showing a single guess row from Online Mastermind, with the guess Red, Red, Green, Green.
A great first guess, assuming you’re playing against a random code and your rules permit the code to have repeated colours, is a “1122” pattern.

There are mathematically-derived heuristics to optimise Mastermind strategy. The first of these came from none other than Donald Knuth (legend of computer science, mathematics, and pipe organs) back in 1977. His solution, published at probably the height of the game’s popularity in the amazingly-named Journal of Recreational Mathematics, guarantees a solution to the six-colour version of the game within five guesses. Ville [2013] solved an optimal solution for a seven-colour variant, but demonstrated how rapidly the tree of possible moves grows and the need for early pruning – even with powerful modern computers – to conserve memory. It’s a very enjoyable and readable paper.

But for my purposes, it’s unnecessary. My solver routinely wins within six, maybe seven guesses, and by nonchalantly glancing at my phone in-between my guesses I can now reliably guess our children’s codes quickly and easily. In the end, that’s what this was all about.

× × × × × ×

Dog; Person

This post is also available as a video. If you'd prefer to watch/listen to me talk about this topic, give it a look.

This blog post is also available as a video. Would you prefer to watch/listen to me tell you about not being a “dog person”, but still loving one dog in particular?

I am not a “dog person”. I’m probably more of a “cat person”.

The Diamond Dogs, from My Little Pony: Friendship is Magic season 1, episode 19. A cartoon image shows three humanoid doglike creatures with gem-encrusted collars and gemstones in their pockets. They're facing off-screen, to the right.
To clarify: when I say “dog person”, I don’t mean “anthropomorphic dog”, like those characters from that weird episode of FiM I talked about back in 2018.

My mum has made pets of one or both of dogs or cats for most of her life. She puts the difference between the two in a way that really resonates for me. To paraphrase her:

When you’re feeling down and you’ve had a shitty day and you just need to wallow in your despair for a little bit… a pet dog will try to cheer you up. It’ll jump up at you, bring you toys, suggest that you go for a walk, try to pull your focus away from your misery and bring a smile to your face. A cat, though, will just come and sit and be melancholy with you. Its demeanour just wordlessly says: “You’re feeling crap? Me too: I only slept 16 hours today. Let’s feel crap together.”

A calico cat on a red ledge against the white outside wall of a building, shadowed by leaves, stares into the camera with a grumpy half-closed-eyes look.
“I hate Mondays. Also any other day of the week with a ‘Y’ in it.”

So it surprised many when, earlier this year, our family was expanded with the addition of a puppy called Demmy. I guess we collectively figured that now we’d solved all the hard problems and the complexities of our work, volunteering, parenting, relationships, money etc. and our lives were completely simple, plain sailing, and stress-free, all of the time… that we now had the capacity to handle adding another tiny creature into our midst. Do you see the mistake in that logic? Maybe we should have, too.

Ruth, in the background, watches on as a French Bulldog puppy jumps up at her two children, a girl and a boy, in a wood-floored hallway.
The kids were, and continue to be, absolutely delighted, especially our eldest who’s been mad about dogs now for well over half her life.

It turns out that getting a puppy is a lot like having a toddler all over again. Your life adjusts around when they need to sleep, eat, and poop. You need to put time, effort, and thought into how to make and keep your house safe both for and from them. And, of course, they bring with them a black hole that eats disposable income.

A French Bulldog puppy sound asleep on a doormat.
Sure they’re cute when they’re asleep, but the rest of the time they’re probably destroying things, pooping, or both. #PuppyOrToddler?

They need to be supervised and entertained and educated (the latter of which may require some education yourself). They need to be socialised so they can interact nicely with others, learn the boundaries of their little world, and behave appropriately (even when they’re not on camera).

A crowd of humans and their dogs watches as Dan uses his finger to lead Demmy the dog through an agility course. She's mid-way through leaping over a yellow pole suspended between two mini traffic cones.
At the end of elementary “puppy school”, we tried some agility course obstacles. Jumps were a success, even for Demmy’s little legs, but she’d far rather hang out inside a tunnel than run through one.

Even as they grow, their impact is significant. You need to think more-deeply about how, when and where you travel, work out who’s responsible for ensuring they’re walked (or carried!) and fed (not eaten!) and watched. You’ve got to keep them safe and healthy and stimulated. Thankfully they’re not as tiring to play with as children, but as with kids, the level of effort required is hard to anticipate until you have one.

An 8-year-old girl in a flowery pink dress lies on her back in a meadow of long dry grass, laughing with her eyes closed. On her belly, Demmy lies, looking up towards the camera with her tongue hanging out.
Whether you’re a human pup or a canine pup, there’s fun to be had in leaping out of long grass to pounce one another.

But do you know what else they have in common with kids? You can’t help learning to love them.

It doesn’t matter what stupid thing they’re illicitly putting in their mouth, how many times you have to clean up after them, how frustrating it is that they can’t understand what you need from them in order to help them, or how much they whine about something that really isn’t that big a deal (again: #PuppyOrToddler?). It doesn’t even matter how much you’re “not a dog person”, whatever that means. They become part of your family, and you fall in love with them.

Under bright blue skies and in a green field, Dan and Demmy stare into the camera. Dan's flushed from the heat and wearing a purple-and-rainbow "WordPress Pride" t-shirt; Demmy's tongue hangs low and she's clearly panting.
Panting and too hot from a long run under the hot summer sun, but loving the opportunity to get out and enjoy the sights and smells of the world. #PuppyOrDan?

I’m not a “dog person”. But: while I ocassionally resent the trouble she causes, I still love our dog.

× × × × × × ×

Milk and Mail Notifications with Flic 2 Buttons

I’ve been playing with a Flic Hub LR and some Flic 2 buttons. They’re “smart home” buttons, but for me they’ve got a killer selling point: rather than locking you in to any particular cloud provider (although you can do this if you want), you can directly program the hub. This means you can produce smart integrations that run completely within the walls of your house.

Here’s some things I’ve been building:

Prerequisite: Flic Hub to Huginn connection

Screenshot showing the location of the enabled "Hub SDK web access open" setting in the Flic Hub settings page of the Flic app.
Step 1. Enable SDK access. Check!

I run a Huginn instance on our household NAS. If you’ve not come across it before, Huginn is a bit like an open-source IFTTT: it’s got a steep learning curve, but it’s incredibly powerful for automation tasks. The first step, then, was to set up my Flic Hub LR to talk to Huginn.

Screenshot showing the Flic Hub SDK open in Firefox. Three modules are loaded: "IR Recorder", "UDP to IR Blaster", and "The Green", the latter of which is open. "The Green" shows JavaScript code to listen for 'buttonSingleOrDoubleClickOrHold' events then transmits them as HTTP POST requests to a 'webHook' URL.
Checking ‘Restart after crash’ seems to help ensure that the script re-launches after e.g. a power cut. Need the script?

This was pretty simple: all I had to do was switch on “Hub SDK web access open” for the hub using the Flic app, then use the the web SDK to add this script to the hub. Now whenever a button was clicked, double-clicked, or held down, my Huginn installation would receive a webhook ping.

Flow chart showing a Flic 2 button sending a Bluetooth 5 LE message to a Flic Hub LR, which sends a Webook notification to Huginn (depicted as a raven wearing a headset), which sends a message to an unidentified Internet Of Things device, "probably" over HTTPS.
Depending on what you have Huginn do “next”, this kind of set-up works completely independently of “the cloud”. (Your raven can fly into the clouds if you really want.)

For convenience, I have all button-presses sent to the same Webhook, and use Trigger Agents to differentiate between buttons and press-types. This means I can re-use functionality within Huginn, e.g. having both a button press and some other input trigger a particular action.

You’ve Got Mail!

By our front door, we have “in trays” for each of Ruth, JTA and I, as well as one for the bits of Three Rings‘ post that come to our house. Sometimes post sits in the in-trays for a long time because people don’t think to check them, or don’t know that something new’s been added.

I configured Huginn with a Trigger Agent to receive events from my webhook and filter down to just single clicks on specific buttons. The events emitted by these triggers are used to notify in-tray owners.

Annotated screenshot showing a Huginn Trigger Agent called "Flic Button C (Double) Details". Annotations show that: (1) "C" is the button name and that I label my buttons with letters. (2) "Double" is the kind of click I'm filtering for. (3) The event source for the trigger is a webhook called "Flic Buttons" whose URL I gave to my Flic Hub. (4) The event receiver for my Trigger Agent is called "Dan's In-Tray (Double) to Slack", which is a Slack Agent, but could easily be something more-sophisticated. (5) The first filter rule uses path: bdaddr, type: field==value, and a value equal to the MAC address of the button; this filters to events from only the specified button. (6) The second filter rule uses path: isDoubleClick, type: field==value, and value: true; this filters to events of type isDoubleClick only and not of types isSingleClick or isHold.
Once you’ve made three events for your first button, you can copy-paste from then on.

In my case, I’ve got pings being sent to mail recipients via Slack, but I could equally well be integrating to other (or additional) endpoints or even performing some conditional logic: e.g. if it’s during normal waking hours, send a Pushbullet notification to the recipient’s phone, otherwise send a message to an Arduino to turn on an LED strip along the top of the recipient’s in-tray.

I’m keeping it simple for now. I track three kinds of events (click = “post in your in-tray”, double-click = “I’ve cleared my in-tray”, hold = “parcel wouldn’t fit in your in-tray: look elsewhere for it”) and don’t do anything smarter than send notifications. But I think it’d be interesting to e.g. have a counter running so I could get a daily reminder (“There are 4 items in your in-tray.”) if I don’t touch them for a while, or something?

Remember the Milk!

Following the same principle, and with the hope that the Flic buttons are weatherproof enough to work in a covered outdoor area, I’ve fitted one… to the top of the box our milkman delivers our milk into!

Top of a reinforced polystyrene doorstep milk storage box, showing the round-topped handle. A metal file sits atop the box, about to be used to file down the handle.
The handle on the box was almost exactly the right size to stick a Flic button to! But it wasn’t flat enough until I took a file to it.

Most mornings, our milkman arrives by 7am, three times a week. But some mornings he’s later – sometimes as late as 10:30am, in extreme cases. If he comes during the school run the milk often gets forgotten until much later in the day, and with the current weather that puts it at risk of spoiling. Ironically, the box we use to help keep the milk cooler for longer on the doorstep works against us because it makes the freshly-delivered bottles less-visible.

Milk container, with a Flic 2 button attached to the handle of the lid and a laminated notice attached, reading: "Left milk? Press the button on the Milk Minder. It'll remind us to bring in the milk!"
Now that I had the technical infrastructure already in place, honestly the hardest part of this project was matching the font used in Milk & More‘s logo.

I’m yet to see if the milkman will play along and press the button when he drops off the milk, but if he does: we’re set! A second possible bonus is that the kids love doing anything that allows them to press a button at the end of it, so I’m optimistic they’ll be more-willing to add “bring in the milk” to their chore lists if they get to double-click the button to say it’s been done!

Future Plans

I’m still playing with ideas for the next round of buttons. Could I set something up to streamline my work status, so my colleagues know when I’m not to be disturbed, away from my desk, or similar? Is there anything I can do to simplify online tabletop roleplaying games, e.g. by giving myself a desktop “next combat turn” button?

Flic Infared Transceiver on the side of a bookcase, alongside an (only slighter smaller than it) 20p piece, for scale.
My Flic Hub is mounted behind a bookshelf in the living room, with only its infrared transceiver exposed. 20p for scale: we don’t keep a 20p piece stuck to the side of the bookcase all the time.

I’m quite excited by the fact that the Flic Hub can interact with an infrared transceiver, allowing it to control televisions and similar devices: I’d love to be able to use the volume controls on our media centre PC’s keyboard to control our TV’s soundbar: and because the Flic Hub can listen for UDP packets, I’m hopeful that something as simple as AutoHotkey can make this possible.

Or perhaps I could make a “universal remote” for our house, accessible as a mobile web app on our internal Intranet, for those occasions when you can’t even be bothered to stand up to pick up the remote from the other sofa. Or something that switched the TV back to the media centre’s AV input when consoles were powered-down, detected by their network activity? (Right now the TV automatically switches to the consoles when they’re powered-on, but not back again afterwards, and it bugs me!)

It feels like the only limit with these buttons is my imagination, and that’s awesome.

× × × × × × ×

Bash+Batch In One File

Today I wanted to write a script that I could execute from both *nix (using Bash or a similar shell) and on Windows (from a command prompt, i.e. a batch file). I found Max Norin’s solution which works, but has a few limitations, e.g. when run it outputs either the word “off” when run in *nix or the word “goto” when run on Windows. There’s got to be a sneaky solution, right?

Here’s my improved version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@goto(){
  # Linux code here
  uname -o
}

@goto $@
exit

:(){
@echo off
rem Windows script here
echo %OS%

Mine exploits the fact that batch files can prefix commands with @ to suppress outputting them as they execute. So @goto can be a valid function name in bash/zsh etc. but is interpreted as a literal goto command in a Windows Command Prompt. This allows me to move the echo off command – which only has meaning to Windows – into the Windows section of the script and suppress it with @.

The file above can be saved as e.g. myfile.cmd and will execute in a Windows Command Prompt (or in MS-DOS) or your favourite *nix OS. Works in MacOS/BSD too, although obviously any more-sophisticated script is going to have to start working around the differences between GNU and non-GNU versions of core utilities, which is always a bit of a pain! Won’t work in sh because you can’t define functions like that.

But the short of it is you can run this on a stock *nix OS and get:

$ ./myfile.cmd
GNU/Linux

And you can run it on Windows and get:

> .\myfile.cmd
Windows_NT

You can’t put a shebang at the top because Windows hates it, but there might be a solution using PowerShell scripts (which use hashes for comments: that’s worth thinking about!). In any case: if the interpreter strictly matters you’ll probably want to shell to it on line 3 with e.g. bash -c.

Why would you want such a thing? I’m not sure. But there is is, if you do.

Do What You’re Bad At

This post is also available as a video. If you'd prefer to watch/listen to me talk about this topic, give it a look.

This blog post is also available as a video. Would you prefer to watch/listen to me tell you about how I’ve learned to enjoy doing what I’m bad at?

There are a great number of things that I’m bad at. One thing I’m bad at (but that I’m trying to get better at) is being more-accepting of the fact that there are things that I am bad at.

Against a pale background, Dan, deep in thought and with a finger to his lips, staring into space. A stylised thought bubble above him shows that he is thinking about himself thinking about himself thinking about himself, and so on (implied to infinity).
I’ve also been thinking about how I’m bad at thinking about how I’m bad at thinking about how I’m bad at thinking about…

I’m pretty bad in a pub quiz. I’m bad at operating my pizza oven without destroying cookware. I’m especially bad at learning languages. I’m appallingly bad at surfing. Every time my work periodically leans in that direction I remember how bad I am at React. And I’ve repeatedly shown that I’m bad at keeping on top of blogging, to the extent that I’ve periodically declared bankruptcy on my drafts folder.

So yeah, pretty bad at things.

But hang on: that assessment isn’t entirely true.

Photograph showing a yellow banana on a pink background. The banana has a silver chain wrapped around it three times. Photo courtesy Deon Black.
I’m also particularly bad at choosing suitable stock photos for use in blog posts.

Being Bad

As a young kid, I was a smart cookie. I benefited from being an only child and getting lots of attention from a pair of clever parents, but I was also pretty bright and a quick learner with an interest in just about anything I tried. This made me appear naturally talented at a great many things, and – pushed-on by the praise of teachers, peers, and others – I discovered that I could “coast” pretty easily.

But a flair for things will only carry you so far, and a problem with not having to work hard at your education means that you don’t learn how to learn. I got bitten by this when I was in higher education, when I found that I actually had to work at getting new information to stick in my head (of course, being older makes learning harder too, as became especially obvious to me during my most-recent qualification)!

Dan, aged around 4, dressed in a duffel coat, bobble hat and gloves, kneeling on a red plastic sledge in a snow-filled garden. The garden is bordered by a wire fence, and in the background a man can be seen scraping icee off a car.
Ignore the fact that you’ve now seen me trying to sledge uphill and just accept that I was a clever kid (except at photography), okay?

A side-effect of these formative experiences is that I grew into an adult who strongly differentiated between two distinct classes of activities:

  1. Things I was good at, either because of talent or because I’d thoroughly studied them already. I experienced people’s admiration and respect when I practised these things, and it took little effort to stay “on top” of these fields, and
  2. Things I was bad at, because I didn’t have a natural aptitude and hadn’t yet put the time in to learning them. We don’t often give adults external reinforcement for “trying hard”, and I’d become somewhat addicted to being seen as awesome… so I shied away from things I was “bad at”.

The net result: I missed out on opportunities to learn new things, simply because I didn’t want to be seen as going through the “amateur” phase. In hindsight, that’s really disappointing! And this “I’m bad at (new) things” attitude definitely fed into the imposter syndrome I felt when I first started at Automattic.

Being Better

Leaving the Bodleian after 8½ years might have helped stimulate a change in me. I’d carved out a role for myself defined by the fields I knew best; advancing my career would require that I could learn new things. But beyond that, I benefited from my new employer whose “creed culture” strongly promotes continuous learning (I’ve vlogged about this before), and from my coach who’s been great at encouraging me towards a growth mindset.

A cake with icing printed with a picture of Dan in a library. Beneath are iced the words "Good Luck Dan".
“Good Luck Dan”, my Bodleian buddies said. But perhaps they should’ve said “Keep Learning Dan”.

But perhaps the biggest stimulus to remind me to keep actively learning, even (especially?) when it’s hard, might have been the pandemic. Going slightly crazy with cabin fever during the second lockdown, I decided to try and teach myself how to play the piano. Turns out I wasn’t alone, as I’ve mentioned before: the pandemic did strange things to us all.

I have no real experience of music; I didn’t even get to play recorder in primary school. And I’ve certainly got no talent for it (I can hear well enough to tell how awful my singing is, but that’s more a curse than a blessing). Also, every single beginners’ book and video course I looked at starts from the assumption that you’re going to want to “feel” your way into it, and that just didn’t sit well with the way my brain works.

Animation showing Dan, wearing a black t-shirt and tracksuit bottoms, playing an upright piano.
90% of what I do in front of a piano might be described as “Dan Mucks About (in B Minor)”, but that’s fine by me.

I wanted a theoretical background before I even sat down at a keyboard, so I took a free online course in music theory. Then I started working through a “beginners’ piano” book we got for the kids. Then I graduated to “first 50 Disney songs”, because I know how virtually all of them sound well enough that I’d be able to hear where I was going wrong. Since then, I’ve started gradually making my way through a transcription of Einaudi’s Islands. Feeling like I’d got a good handle on what I was supposed to be doing, I then took inspiration from a book JTA gave me and started trying to improvise.

Most days, I get no more than about 10 minutes on the piano. But little by little, day by day, that’s enough to learn. Nowadays even my inner critic perfectionist can tolerate hearing myself play. And while I know that I’ll probably never be as good as, say, the average 8-year-old on YouTube, I’m content in my limited capacity.

Three books on a blue-and-white tablecloth: John Thompson's Easiest Piano Course (Part One), First 50 Disney Songs, and Essential Einaudi - Islands. Beneath them sits a simplified diagram showing the circle of fifths.
Let’s start at the very beginning. (A very good place to start.)

If I’m trying to cultivate my wonder syndrome, I need to stay alert for “things I’m bad at” that I could conceivably be better at if I were just brave enough to try to learn. I’m now proudly an “embarrassingly amateur” pianist, which I’m at-long-last growing to see as better than a being non-pianist.

Off the back of that experience, I’m going to try to spend more time doing things that I’m bad at. And I’d encourage you to do the same.

× × × × ×

Mathematics of Mid-Journey Refuelling

I love my electric car, but sometimes – like when I need to transport five people and a week’s worth of their luggage 250 miles and need to get there before the kids’ bedtime! – I still use our big ol’ diesel-burning beast. And it was while preparing for such a journey that I recently got to thinking about the mathematics of refuelling.

Car display showing "Please refuel. Range: 40ml"
I don’t know why you’d measure range in millilitres in the first place, but I’m hearing that I ought to fill up the car before we go.

It’s rarely worth travelling out-of-your-way to get the best fuel prices. But when you’re on a long road trip anyway and you’re likely to pass dozens of filling stations as a matter of course, you might as well think at least a little about pulling over at the cheapest.

You could use one of the many online services to help with this, of course… but assuming you didn’t do this and you’re already on the road, is there a better strategy than just trusting your gut and saying “that’s good value!” when you see a good price?

It turns out this is an application for the Secretary Problem (and probably a little more sensible than the last time I talked about it!).

A woman's hand reaches for one of four fuel pump nozzles. Photo by Gustavo Fring, used under the Pexels License.
If you can’t decide which nozzle is best, mix up a cocktail of them all. #TerribleProTip

Here’s how you do it:

  1. Estimate your outstanding range R: how much further can you go? Your car might be able to help you with this. Let’s say we’ve got 82 miles in the tank.
  2. Estimate the average distance between filling stations on your route, D. You can do this as-you-go by counting them over a fixed distance and continue from step #4 as you do so, and it’ll only really mess you up if there are very few. Maybe we’re on a big trunk road and there’s a filling station about every 5 miles.
  3. Divide R by D to get F: the number of filling stations you expect to pass before you completely run out of fuel. Round down, obviously, unless you’re happy to push your vehicle to the “next” one when it breaks down. In our example above, that gives us 16 filling stations we’ll probably see before we’re stranded.
  4. Divide F by e to get T (use e = 2.72 if you’re having to do this in your head). Round down again, for the same reason as before. This gives us T=5.
  5. Drive past the next T filling stations and remember the lowest price you see. Don’t stop for fuel at any of these.
  6. Keep driving, and stop at the first filling station where the fuel is the same price or cheaper than the cheapest you’ve seen so far.
Dan sitting in the driving seat of a car, doing maths on a portable whiteboard.
Obviously you should take care doing maths on the road. Don’t drink and derive!

This is a modified variant of the Secretary Problem because it’s possible for two filling stations to have the same price, and that’s reflected in the algorithm above by the allowance for stopping for fuel at the same price as the best you saw during your sampling phase. It’s probably preferable to purchase sub-optimally than to run completely dry, right?

Of course, you’re still never guaranteed a good solution with this approach, but it maximises your odds. Your own risk-assessment might rank “not breaking down” over pure mathematical efficiency, and that’s on you.

× × ×

Wonder Syndrome

Ruth wrote an excellent post this month entitled Wonder Syndrome. It attempts to reframe imposter syndrome (which is strongly, perhaps disproportionately, present in tech fields) as a positive indicator that there’s still more to learn:

Being aware of the boundaries of our knowledge doesn’t make us imposters, it makes us explorers. I’m going to start calling mine “Wonder Syndrome”, and allowing myself to be awed by how much I still have to learn, and then focusing in and carrying on with what I’m doing because although I may not reach the stars, I’ve come a long way up the mountain. I can learn these things, I can solve these problems, and I will.

This really resonated with me, and not just because I’ve totally bought into the Automattic creed, which literally opens with the assertion that “I will never stop learning”. (Other parts of the creed feel like they parallel Ruth’s post, too…)

Dan and Jacob look at a piece of code together; Dan is smiling but Jacob looks disgusted.
I don’t recall exactly what I’m advising a fellow Three Rings developer to do, here, but I don’t think he’s happy about it.

I just spent a week at a Three Rings DCamp (a “hackathon”, kinda), and for the umpteenth time had the experience of feeling like everybody thinks I know everything, while on the inside I still feel like I’m still guessing a third of the time (and on StackOverflow for another third!).

The same’s true at work: people ask me questions about things that I suppose, objectively, are my “specialist subjects” – web standards, application security, progressive enhancement, VAT for some reason – and even where I’m able to help, I often get that nagging feeling like there must be somebody better than me they could have gone to?

Pair of Venn diagrams. The first, titled "In my head", shows "things Dan is good at" as a subset of "things others are good at". The second, titled "Reality", shows an intersection between "things others are good at" and "things Dan is good at" but plenty of unshared space in each.
You’ve probably seen diagrams like this before. After all: I’m not smart or talented enough to invent anything like this and I don’t know why you’d listen to anything I have to say on the subject anyway. 😂

You might assume that I love Ruth’s post principally because it plays to my vanity. The post describes two kinds of knowledgeable developers, who are differentiated primarily by their attitude to learning. One is satisfied with the niche they’ve carved out for themselves and the status that comes with it and are content to rest on their laurels; the other is driven to keep pushing and learning more and always hungry for the next opportunity to grow. And the latter category… Ruth’s named after me.

Woman on laptop, looking concerned towards camera, captioned "are you even good enough to have imposter syndrome?"
Wait, what if I’m not Have I been faking it this entire blog post?

Bnd while I love the post, my gut feeling to being named after such an ideal actually makes me slightly uncomfortable. The specific sentence that gets me is (emphasis mine):

Dans have no interest in being better than other people, they just want to know more than they did yesterday.

I wish that was me, but I’m actually moderately-strongly motivated by a desire to feel like I’m the smartest person in the room! I’m getting this urge under control (I’m pretty sure I was intolerable as a child and have been improving by instalments since then!). Firstly, because it’s an antisocial pattern to foster, but also because it limits my ability to learn new things to have to go through the awkward, mistake-filled “I’m a complete amateur at this!” phase. But even as I work on this I still get that niggling urge, more often than I’d like, to “show off”.

Of course, it could well be that what I’m doing right now is catastrophising. I’m taking a nice thing somebody’s said about me, picking the one part of it that I find hardest to feel represents me, and deciding that I must be a fraud. Soo… imposter syndrome, I guess. Damn.

Or to put it a better way: Wonder Syndrome. I guess this is another area for self-improvement.

(I’m definitely adopting Wonder Syndrome into my vocabulary, as an exercise in mitigating imposter syndrome. If you’ve not read Ruth’s post in full, you should go and do that next.)

× ×