Today’s the day I learn how to create custom roles in reStructuredText. There’s already documentation on how to do this. I’m just applying it for my specific case.
Prologue: Setup
Install some stuff if you want to play along.
Some of the requirements are specific to my writing flow.
For experimentation, I copied the build code from my Neovim rst plugin into the site’s Invoke task file. Easier than updating remote plugins and restarting the editor with every change.
Then I use Invoke to do the transform:
Some variation of this is bound to work for you.
Let’s get started!
What even is a role?
First, we need the background. There’s this thing called interpreted text. It’s a reserved bit of functionality for specially marked text. Folks coming to reStructuredText from Markdown mostly know it as the weird reason they have to use double backticks for code
.
Interpreted text has all sorts of fancy potential. I mainly know it for the fact that rst links use it. Unless told otherwise, Docutils treats interpreted text as a citation.
It assumes any interpreted text is :title-reference:
— that is, it references the title of a book, movie, song, or other publication. The cite
element is a perfectly reasonable choice for that.
But what if you aren’t specifically talking about a title? Roles provide an explicit label for your interpreted text.
What’s a :term:
in rst? Nothing. I made it up. Seems like a good role for when I introduce a new name and I want it to stand out.
I need to define the role to use it. Otherwise?
Docutils embeds an error message below the offending block
So up at the top of my document use the role directive to create :term:
and register it with the parser.
Now that Docutils knows about the role, it can turn it into HTML.
It still doesn’t have any inherent meaning, but I can put some style rules on it so that anything I label with the :term:
role shows up a little differently.
Inline roles in your document
If I want the term to stand out a little more, I can adjust my role definition.
Now it inherits from the :strong:
role, keeping the "term"
CSS class.
You can inherit from any role. That makes it a nice way to create aliases or slight variations to existing roles.
But I want to get fancy. Let’s look at defining reStructuredText roles in Python.
Defining roles in your code
Defining a role has two main steps. Okay, three. Because first we need to import some libraries.
Now we create a function that knows what to do when given a role and some preprocessed parameters.
That’s quite a function signature to take in without context, so here’s a breakdown of what got sent when Docutils saw my first :term:`Roles
:
parameter | value | explanation |
---|---|---|
name |
term |
the role name |
rawtext |
:term:`Roles |
all text input including role and markup |
text |
Roles |
the interpreted text content |
lineno |
103 |
the interpreted text starts on this line |
inliner |
<docutils…Inliner object at …> |
the object that called this function |
options |
{} |
a dictionary of customization options |
content |
[] |
a list of strings containing text content |
I won’t pretend I know how to use all these yet. That’s okay. role_term
only cares about three:
rawtext
text
options
— just in case
I chose to mirror the inline directive I made earlier, creating a strong
node with a class of "term"
.
Anyone calling role_term
expects a tuple with two node lists: one for content, and another holding any error nodes I may need to create. In this case the content list has my term node and the error list is empty.
With our role implementation defined, we register it and the name associated with it.
I don’t need my inline role
directive anymore, so I remove it. Registering role_term
makes it available to every document processed by this particular Python script.
Okay, now I basically know how to implement a reStructuredText role. Let’s keep going.
:tag:
references
I link to tags on this site frequently. Since I’m the main audience for this site, it’s mostly to give me a shortcut to related content. But hey it may help you find related content to if you happen to click through.
Couple of problems with those tag links, though. First off, they look exactly like every other link in my published HTML. It would be nice for them to stand out a bit when I’m reading. Second, they look like every other link in my post source. It would be nice for them to stand out a bit when I’m writing.
So let’s make a :tag:
reference role.
I thought about putting the #
in CSS, but not every p-category
is a tag. I can always change my mind later, maybe make a distinct tag
CSS class.
It looks similar to :term:
, except because I’m referencing something I use a reference
node and give it a link to that tag’s page as refuri
. The p-category
class is a microformats2 thing for IndieWeb. I also decided to prefix my tag text with the traditional octothorpe used to mark tags out in the wild.
Oh yes that is much nicer to read than a standard reStructuredText link.
There’s my p-category
class, along with an unsurprising reference
— since it’s a clear way to indicate the reference node I used — and a slightly confusing external
class. Pretty sure that means “external to the document.”
A :kbd:
role
Something I need rather often is a way to indicate keyboard input. Control c, stuff like that.
Well that was easy. A bit verbose, but okay. That’s not the real problem though.
There’s a perfectly good <kbd>
element
This blog is HTML, right? Can’t I just use the kbd
element in my role?
Yes, but kind of no. It’s considered poor form to put raw HTML in your output nodes. Docutils writes all sorts of content, and a <kbd>
would be pretty ungainly sitting in a PDF. Ideally you’d take care of writing HTML in an HTML Writer. Unfortunately, I have no idea how to work an HTML Writer yet.
But we can output raw HTML in a role implementation. It would be frowned on slightly less if we flagged it as a raw role.
Better pull in the html standard library and escape that text. Otherwise I’d feel awful silly when talking about indenting with >>
in Vim or something and it breaks the whole page.
Yeah, that works. It’s not too bad to look at while writing.
And there we go. An honest to goodness <kbd>
element. And :raw-kbd:
will be easier to search for if and when I get around to custom HTML Writers.
Figuring out a role for keyboard input was the reason I started writing this post — though my favorite new role is :tag:
. Anyways, I think this is a good spot to stop writing and start editing.
Wrap it up
…pardon me while I copy those role functions back into my Neovim plugin…
Well that was fun. I wanted a role for keyboard input, and I got it. Plus, my tags are a little easier to find in the page. And I have a :term:
role for when I’m feeling pedagogical.
Cool.
Roles are just a first step in customizing Docutils output. No idea when I’ll get to the rest. You can learn more for yourself with Docutils and heavily customized publishing environments like Sphinx.
Me, I’m just having a grand time embedding this whole authoring flow in the middle of my Hugo site. May want to think about a new theme though if I’m going to continue with Hugo. Perhaps borrow from Alexander Carlton’s Hugo B-side.
Backlinks
Got a comment? A question? More of a comment than a question?
Talk to me about this page on: mastodon
Added to vault 2024-01-15. Updated on 2024-02-02