Skip to article frontmatterSkip to article content

Re-building my blog with MySTMD

2i2c
Project Jupyter

Wow it has been a long time since I’ve last-written here. It turns out that having two small children and a very demanding job means you don’t have as much time for blogging. But that’s a whole different blog post...

I’ve decided to convert my blog to use the new MyST Document Engine. This is part of a dogfooding experiment to see what’s possible with MyST, since it’s where the Jupyter Book project is heading, and I want to see how close to “production-ready” we are already.

To begin, I wanted to share a few things I learned today as I tried to re-tool my old blog for use with MyST MD.

There’s no blog functionality in MyST yet

As MyST is still quite young, there’s no out-of-the-box functionality for MyST (see jupyter-book/mystmd#840 for the issue tracking that). So, I wanted to accomplish at least two things with my initial transfer:

  1. Generate a list of recent blog posts that I can insert into a few places in my site.

  2. Generate an RSS feed that keeps the same URLs, content, etc.

What didn’t work: using the parsed MyST documents

My first thought was to use a MyST plugin that defines a directive I could use to insert a list of blog posts. However, I learned that MyST plugins have no way to access all of the parsed documents at build time (see this issue about accessing all of the parsed documents to track that one).

Fortunately, @rowanc1 made me realize that I could just manually parse my blog files and use that to build up something like a blog list. So that’s what the rest of this post is about.

You can run scripts in JavaScript as part of your MyST build

The MySTMD plugins documentation shows a few examples for how to define your own MyST plugins. These are little JavaScript files that get executed every time you build your MyST site.

The easiest thing to do here would be to write a JavaScript plugin for MyST that I can attach to my site build. You could use a JS library to parse the markdown files in blog/, grab their YAML metadata, and return MyST AST structure that would be inserted into the document. But I’m not too comfortable with JavaScript, and I found two ways that are much hackier, but much more accessible for somebody that is comfortable with Python 😎.

Write a MyST extension in Python

I bet most folks don’t know that you can write MyST extensions entirely in Python (or any other language that you can execute locally). Here is the MyST documentation on writing an executable MyST extension.

Executable extensions are treated like a black box in MyST - the MyST build process simply executes a file that you specify in myst.yml, and treats anything printed to stdout as MyST AST that will be inserted back into the MyST document.

How do you know what MyST AST looks like?

I mention “all you need to do is output MyST AST”, but what does that even mean? The MyST AST is the abstract structure of a MyST document. It has all of the relevant information about the content in a document, as well as all the semantic tags for different types of content that can exist (e.g., “italics”, or “admonition boxes”).

When a MyST Markdown document is parsed, the result is MyST AST. You can see a ton of examples of AST for various MyST markdown in the MyST guide. Just look for the litte “interactive demo” boxes that show off sample MyST Markdown.

In my case, I needed a list of blog posts, so I found the relevant AST for what this looks like at the following locations:

Turning this into a Python plugin for MyST

With this in mind, I wrote a little Python extension that:

  1. At build time, parses all of my blog post markdown files and extracts metadata from their YAML headers.

  2. Defines a bloglist directive that will insert a list of blog posts where it exists.

  3. Manually converts the blog post metadata into MyST AST that it prints to stdout.

As a result, when I call the directive in my blog, it will replace the directive with whatever AST is spit out by the Python script. You can take a look at the entire Python script here.

Now I can insert lists of blog posts wherever I like, for example here’s a list of the latest three:

The slow boring of hard boards in open source

Max Weber famously wrote that politics is "a strong and slow boring of hard boards." In [Why it took 4 years to get a lock files specification](https://snarky.ca/why-it-took-4-years-to-get-a-lock-files-specification/), Brett Cannon demonstrates how the same principle applies to technical coordination in open source. Python recently adopted [PEP 751](https://peps.python.org/pep-0751/) for lockfile specification. Doing so

Date: October 21, 2025 | Author: Chris Holdgraf
My system for beating jet lag

I travel internationally a lot, which means I deal with a lot of jet lag. This post is a quick summary of a system I've found helpful, based on [this paper](https://www.frontiersin.org/articles/10.3389/fphys.2019.00927/full). The basic idea is to shift your body's internal clock by timing your exposure to light, exercise, and melatonin around

Date: October 18, 2025 | Author: Chris Holdgraf
Why open source foundations try to fund systems, not development

This is a brief reflection on something that I've been hearing consistently from the Linux Foundation and its member projects as part of serving on the [Board of the Jupyter Foundation](https://jupyterfoundation.org). Here's a point that originally surprised me when I heard it: > Most foundations within the Linux Foundation network recommend

Date: May 31, 2025 | Author: Chris Holdgraf

Adding an RSS feed

Because I’m running Python to define my MyST plugin, I can also use Python to build a custom RSS feed! This was relatively easy since I’d already parsed the metadata from each file.

I found the python-feedgen package, which is a little helper package for generating RSS feeds in Python. Since my MyST plugin was already written in Python, I just added a few more lines to do so.

Annoyingly, you cannot just tell MyST to put a file in a particular location (see jupyter-book/mystmd#1196 tracking this one). So I had to manually move this file to my build output folder in my GitHub action. Hopefully this functionality gets updated soon. Here’s what that looks like:

deploy.yml
    # Move RSS feeds to output folder
    - name: Move RSS feeds
      run: |
        cp atom.xml _build/html/atom.xml
        cp rss.xml _build/html/rss.xml

    # If we've pushed to main, push the book's HTML to github-pages