skip to main content

Generating a Blogroll With OPML in Hugo

published icon  |  category icon webdesign

tags icon hugo rss blogging opml

Blogrolls have been on my mind lately. It’s a rather fancy word for a more common but far from mundane corner of your website, called a links section. Ever since my first website in 1998—you can marvel at my old junk at the Brain Baking Museum—I’ve had a links section. But for some reason, on the latest revision of this site, it’s been gone for years.

A links page is fun for a couple of reasons:

  • It shows what the website owner is interested in;
  • It’s a chance to give a shout-out to fellow websiters (if I’d type blogger here, I’d be too limiting);
  • It can be used to play the im-bored-lets-click-through-stuff game (isn’t that the purpose of those webrings?).

Especially since I’ve been encountering my own site in the midst of the links of others (thank you!), I felt a bit bad for not returning the favor. Enter the /links page!

After getting nostalgic (again…), I rummaged through my digital archive to dig up my 2007 browser bookmarks. It’s a lot of fun to scroll through and instantly gives an impression of what I was up to back then (Infinity engine game modding, Nintendo DS programming, Wizardry 8 guide writing, retro gaming, BSD and Linux kernel programming, Magic the Gathering, hip-hop).

So much yet so little has changed! It’s a bit embarrassing. Or should I be proud? I’m not sure. I feel a bit conflicted when looking back at it. Like Brit Butler wrote in “Deliberate Action":

A tremendous amount has happened, but I feel like I’ve lost the boy I remember from college a little. He was excited about things: video games, music, common lisp, poetry.

Anyway. As Ruben would write: I digress.

Hugo and XML parsing

Since nowadays I manage my digital intake via my RSS reader, which already contains an XML-based list of links in an .opml file, I took a stab at automating this process.

Recently, Hugo implemented XML Data support (part of the 0.92.0 release). This means chucking an XML file in /data automatically exposes it in the template engine, making iterating over entries using {{ range $.Site.Data.blogroll.body.outline }} trivial. Or so I thought.

The supplied example in the commit message of the patch is very brief and only covers retrieving contents of XML tags: <title>sup</title> can be accessed via the not-so-special {{ .title }} shortcode, as it becomes a Go property. But OPML outline XML is something like this:

<outline text="Brain Baking" title="Brain Baking" description="" type="rss" version="RSS" htmlUrl="https://brainbaking.com/" xmlUrl="http://brainbaking.com/index.xml"/>

All attributes, no content: the outline tag is auto-closed (/>). I had no clue how to fetch that data, as dumping the entire variable printed a map of keys prepended with a dash. Why? It seems that someone else submitted an issue to the hugoDocs repository, so I took the effort to summarize changes into a pull request. Accessing properties, apparently, is done via {{ index $body.outline "-title" }}.

I’ve never used Hugo’s index function before. Having to prepend attribute names with - feels awkward. I presume the changes are still young and a bit untested. Hopefully it’ll evolve and stabilize over time. The problem right now is a total lack of documentation. Hopefully this blog post helps alleviate the problem a bit.

NetNewsWire OPML exports

Where does the OPML file come from? NetNewsWire keeps track of local subscriptions (“On My Mac”) in a file somewhere (/Users/me/Library/Containers/com.ranchero.NetNewsWire-Evergreen/Data/Library/Application\ Support/NetNewsWire/Accounts/OnMyMac/Subscriptions.opml). Copying over suffices, but of course does not auto-update when I add more feeds in the reader. Other options I’ve considered:

  • Softlinks. Nothing but trouble while committing into git, tried a lot of different approaches;
  • Hardlinks. Borked in MacOS. As soon as NetNewsWire makes a change, the hardlink is severed;
  • Third party hardlink cmd-based solutions. Didn’t work;
  • Write a script that parses it into JSON and add to the CI. Too complicated;
  • Add a stupid copy command into the user’s crontab.

The last option is currently in use, although not ideal. Oh well.

Another problem is the lack of metadata, or description information. The /links page contains little information of the link itself that way, especially if the blogger’s <title/> tag is a bit… woozy. Like, “Articles”, for instance. Any RSS feed, such as mine, contains a (proper) title and description:

<channel>
    <title>Brain Baking</title>
    <link>http://localhost:1313/</link>
    <description>Freshly Baked Thoughts by Wouter Groeneveld </description>
    ...
</channel>

It somehow doesn’t get saved into the OPML, although there’s a description tag—it’s hardcoded to the empty string. Why? I’d love to hack away in the Swift code but could use some help.


Addendum, 31th March 2022: Auto-generating decent links proved to be too difficult. The addition of a self-hosted RSS-Bridge, which allows me to generate RSS feeds based off Instagram/Twitter feeds, proved to be too much. The resulting list of links is a nondescript mess. In the end, I decided to curate the /links page manually.


Bonus material

Hey, this site also supports dark mode from now on! It seems to be a thing and after discovering MacOS Montery’s “Auto” Appearance switch setting, I couldn’t resist. Enjoy!

Switching from light to dark mode in MacOS.

I'm Wouter Groeneveld, a Brain Baker, and I love the smell of freshly baked thoughts (and bread) in the morning. I sometimes convince others to bake their brain (and bread) too.

If you found this article amusing and/or helpful, you can buy me a coffee - although I'm more of a tea fan myself. I also like to hear your feedback via Mastodon or e-mail. Thanks!