johnke.me

Hausu

Poster for Hausu
Watched on June 28, 2025
Rating:

The visual invention in this film is unreal. Perfect commitment to the bit, regardless of how they had to achieve the shot. Which means that sometimes the effects get a little ropey and we cross into Garth Merenghi’s Darkplace territory. It’s occasionally incredfibly camp and rarely classicly scary but it’s 100% a vibe.

Importing Linkding bookmarks into Hugo

One of my goals for the second half of 2025 is to reduce the amount of duplication in my life. I have a half a dozen read/watch/listen-it-later services. My tasks are strewn across four different productivity systems. I’m too old for this kind of nonsense, and I’d like to get this down into something more manageable.

A nice easy starting point for decluttering: the links system on this site. I migrated from Pinboard to Raindrop to a Linkding instance at bookmarks.johnke.me. That’s where I store my bookmarks. But what about if I find something I want to share on this site? Well for that, I run a bash script (bin/newlink.sh) passing the URL and it will generate a Hugo post for that link. Last year, with version v0.126.0, Hugo introduced a new feature: content adapters. This allows Hugo, a static site generator, to dynamically generate content from another source. In their examples, they just use a dict but you can also use the output of an API. An API like the one provided by Linkding?

Setting this up was shockingly easy!

First, I put some Linkding config in Hugo’s top-level config.yaml:

params:
  ...
  linkding:
    url: "https://bookmarks.johnke.me/api/bookmarks/?q=%23johnke.me"
    token: "MY_API_TOKEN"
  ...

The ?q==%23johnke.me means that any bookmark I tag with johnke.me will be picked up by the content adapter.

Then I put a _content.gotmpl at the top-level of the section where I want my content created. So in my case, I have a content/link/_content.gotmpl that looks like this:

{{/* Fetch bookmarks from Linkding */}}
{{/* Set using HUGO_PARAMS_LINKDING_URL, HUGO_PARAMS_LINKDING_TOKEN or in Hugo config */}}
{{/* Full endpoint URL with any filters, e.g. "https://bookmarks.johnke.me/api/bookmarks/?q=%23dorkus" */}}
{{ $URL := .Site.Params.linkding.url }}
{{ $TOKEN := .Site.Params.linkding.token }}

{{/* Get remote data. */}}
{{ $data := dict }}
{{ $headers := dict "Authorization" (print "Token " $TOKEN) }}
{{ $opts := (dict "headers" $headers) }}
{{ with try (resources.GetRemote $URL $opts) }}
  {{ with .Err }}
    {{ errorf "Unable to get remote resource %s: %s" $URL . }}
  {{ else with .Value }}
    {{ $data = . | transform.Unmarshal }}
  {{ end }}
{{ else }}
  {{ errorf "Unable to get remote resource %s" $URL }}
{{ end }}

{{/* Add pages and page resources. */}}
{{ range $data.results }}
  {{/* Skip entries with empty titles */}}
  {{ if .title }}
    {{/* Add page. */}}
    {{ $content := dict "mediaType" "text/markdown" "value" .notes }}
  }}
    {{ $dates := dict
      "date" (time.AsTime .date_added)
      "lastmod" (time.AsTime .date_modified)
    }}

    {{/* For tags, remove `johnke.me` */}}
    {{ $tags := slice }}
    {{ range .tag_names }}
      {{ if ne . "johnke.me" }}
        {{/* Dashes to spaces */}}
        {{ $tag := replace . "-" " " }}
        {{ $tags = $tags | append $tag }}
      {{ end }}
    {{ end }}

    {{/* I use the 'link' parameter to point to the URL */}}
    {{ $params := dict
      "link" .url
      "tags" $tags
    }}

    {{/* Create a safe filename from the title */}}
    {{ $safePath := .title | urlize }}
    {{ if not $safePath }}
      {{ $safePath = printf "bookmark-%d" (time.AsTime .date_added).Unix }}
    {{ end }}

    {{ $page := dict
      "path" $safePath
      "kind" "page"
      "title" .title
      "dates" $dates
      "params" $params
      "content" $content
    }}
    {{ $.AddPage $page }}
  {{ else }}
    {{ warnf "Skipping bookmark with empty title: %s" .url }}
  {{ end }}
{{ end }}

Finally, since this whole thing is driven by github actions, I added a schedule to my action definition so twice a day, it will regenerate the site with any new links I’ve added:

on:
  ...
  schedule:
    # Runs every day at 2:00 AM and 2:00 PM UTC
    - cron: '0 2,14 * * *'
  ...

And that’s it! I have links coming directly in from Linkding and I have one canonical source of truth for all my bookmarks and one less point of redundancy!

Neat!

The 5,000 Fingers of Dr. T

Poster for The 5,000 Fingers of Dr. T
Watched on June 26, 2025
Rating:

Does so much with so little. The songs are forgettable and unexciting but the visuals and the playfulness of the language are pure Seuss. Like as close as we’ll get to a live-action Oh The Places You’ll Go. And Hans Conried’s voice is perfect as Dr. Terwilliker. He’s having so much fun hamming it up as the cartoon baddy and his enjoyment is infectious.

Demented. And I loved it.

28 Years Later

Poster for 28 Years Later
Watched on June 24, 2025
Rating:

Not so much a zombie film as much as a coming-of-age film dressed in folk horror clothing, and it’s no less effective for it. Newcomer Alfie Williams does a really impressive job as Spike, a 12 year old who born into an infected world, and the action takes up as his father brings him on his first hunting trip to the mainland. He’s given a complicated emotional arc and he portrays it heroically, with a wonderful, nuanced performance.

I’m a sucker for this kind of speculative setup. What does the world look like for the next generation after the initial film? Questions that made the recent Apes films so interesting. But once we get outside of this, the story of 28 Years Later doesn’t really take us to any surprising places and a lot of it can feel screenwriter-formulaic (oh you’re going to hit us with the duality of birth and death? Cool cool cool. Samson and Delilah? Oh wow). But one thing I love about Danny Boyle is how he is capable of elevating the blandest trash. Nowhere is this better demonstrated than in the final act: a wonderful, ecstatic celebration of life and death and mortality that hit me so hard.

But the coda. Yeesh. I didn’t realise this was already planned to be a trilogy, so the final minutes felt like a giant fuck you to Sony, daring them to use this tonally awful, morally misjudged bullshit as the starting point for the next film. But apparently the sequel has already been filmed? Like I said, yeesh.

Delirious

Poster for Delirious
Watched on June 22, 2025
Rating:

There’s no question about the greatness on display here, but even by 80s standards, the opening of this is a problematic watch in 2025.

Pirates of Silicon Valley

Poster for Pirates of Silicon Valley
Watched on June 22, 2025
Rating:

Hits all of the anecdotes and essential events through a series of incredibly brief, sometimes barely-connected vignettes. I know it’s going to be divisive but I personally like how sometimes the film can’t figure out a “show, don’t tell” way of highlighting the importance of a moment, so it will break the fourth wall and have characters pop out of a scene to talk about what we’re seeing. It’s cheap but it feels very 90s and very comforting. Also feels like it maybe could have benefited from a slightly better budget? This is especially true of the needle-drops (although Burnin’ Down the House to close out the film is an all-timer).

More entertaining than it should have been.

ØXN - The Trees They Do Grow High - YouTube