Between reading Python documentation and exploring Nikola, I've been looking at RST a lot. Let's figure out the basics.
- What is it?
- How do I use RST in my blog?
- What does it look like?
- What did I miss?
reStructuredText is a lightweight formatting language with a cumbersome name. You mostly see it in Python docstrings, because it's the standard format for Python documentation. Through site generators and Sphinx, RST also shows up behind the scenes in blogs, projects, and technical books.
Nothing about RST limits it to technical writing — well, except that most nontechnical folks aren't installing special Python libraries to write Hugo posts.
Anyways. The essentials of the RST format are easy enough that it's suited for general writing.
If you already blog with Nikola or Pelican, you are all set. Those site generators natively support reStructuredText.
Hugo will build .rst content if you have rst2html.py installed.
$ pip install docutils
Emacs and Vim both include RST support built-in. Visual Studio Code users can find a useful plugin. But all you need is a plain text editor, preferably with automatic indentation.
The HTML generated by rst2html.py has its own special classes. My home-grown Hugo theme supports none of those classes, of course. I couldn't figure out how to export the Docutils default stylesheet this morning.
So I made a document and grabbed the CSS rules from there for my own nefarious purposes.
$ echo "hey\n" | rst2html.py >> sample.html
Although RST is readable, more blog-type folks are familiar with a Markdown flavor. I know I was more familiar with Markdown when I started this. Once you get the hang of it, you may find that RST has its charms.
More than enough to write one of my blog notes.
It all starts with paragraphs. Plain text, separated by empty lines. The text lines of a paragraph are wrapped together.
Indent your paragraph if you want a nice blockquote.
You can emphasize text in a paragraph using asterisks. Double asterisks give more emphasis. You can wrap multiple words to emphasize all of them. I think doing that dilutes the effect, though. You end up with something that looks more like a conspiracy-themed newsletter. But hey. If that's the look you're going for? Have fun!
It all starts with paragraphs. Plain text, separated by empty lines. The text lines of a paragraph are wrapped together. Indent your paragraph if you want a nice blockquote. You can *emphasize* text in a paragraph using asterisks. Double asterisks give **more** emphasis. You can wrap multiple words to *emphasize all of them*.
Use double backticks for inline literals — characters displayed in a monospace font and often used to indicate code. This is a little confusing after Markdown, which uses a single backtick for literals. But RST uses those for interpreted text.
Use ``double backticks`` for inline literals — characters displayed in a monospace font and often used to indicate code. This is a little confusing after Markdown, which uses a single backtick for literals. But RST uses those for `interpreted text`.
What's interpreted text? Well, it can mean a few things depending on the context of what's in and around it. You could even define your own with Python. Not today, though.
rst2html.py transforms a lone bit of `interpreted text` to <cite>interpreted text</cite>. The citation tag is used in HTML for referencing creative work: books, songs, blog posts.
We already know what a basic bullet list looks like.
- You have some lines
- Each line starts with a special character and a space
- I used * but RST allows a few:
- The important thing is to be consistent for a list or sublist
- oh, and you can do sub lists with indentation!
- but you need blank lines between list levels
* You have some lines * Each line starts with a special character and a space * I used ``*`` but RST allows a few: - ``*`` - ``-`` - ``+`` * The important thing is to be consistent for a list or sublist - oh, and you can do sub lists with indentation! - but you *need* blank lines between list levels
We've got the basics. After these next few items, I have about 80% of everything I ever wrote on this site covered.
Most blog generators demote your headers by at least one level. That way your post title goes at the top of the heirarchy. It also means my level three section headers generate <h4> tags! So don't go overboard with subsections.
You've been looking at section headers already, so it seems silly to put examples here. Plus it messes up the document structure.
You need two lines to make a section header. The text of the header itself forms the first line. Use the text of the header itself for the first line. In the second line, put enough non-alphanumeric characters to match your header's length. Pick any you like — well, any from the set of = - ` : ' " ~ ^ _ * + # < > — as long as you stay consistent.
What does it look like? ======================= section 3
First symbols picked, so it's a level one header.
A little more ------------- section 3.1
I picked a new symbol for the indicator, so this is a level two header.
Headers and sections ~~~~~~~~~~~~~~~~~~~~ section 3.1.1
Another new symbol means another level, taking us to a level three header.
Images and figures ~~~~~~~~~~~~~~~~~~ section 3.1.2
These use the same symbol I used for Headers and sections, so this is another level three header.
Directives ---------- section 1.2
Oh hey, remember this symbol? We're back up to level two!
This is the only area where RST feels significantly more cumbersome to me than Markdown or AsciiDoc. At least it's pretty to look at.
I already have my own shortcodes for images in Hugo. Oh, and the special logic for cover pictures. Jeez I have my work cut out for me if and when I migrate to another generator.
Still, image are a pretty fundamental part of blogging. It would feel strange to skip them.
.. image:: worst-cat.png :alt: Text reads "This is the worst cat." Photo is a baby hippo :target: https://worstcats.tumblr.com/post/97243616862/this-is-the-worst-cat
Look, more explicit markup! This calls the image directive with worst.cat.png as an argument and a few options specified with what RST calls a field list.
You can make the image a link with :target:, which is nice.
I prefer the HTML figure for my images. It allows me to add a readable caption, which is a great spot for attribution.
.. figure:: worst-cat.png :alt: Text reads "This is the worst cat." Photo is a baby hippo via the `Worst Cats`_ Tumblr blog
This directive is conceptually much closer to what I'm thinking of. You even get a whole paragraph to set the caption. Text after the first paragraph becomes the legend. Interested parties can read the figure documentation for more details about that.
Unfortunately it's not really a <figure>. This is a div.figure holding an img and a p.caption instead of a <figcaption>. As a purist, I recognize that I must eventually fix this.
Tables are very handy for summarizing information. RST allows extremely complex table formatting. Fortunately for me, I never use extremely complex table formatting. simple-tables work just fine.
========= ================= Generator Supports RST ========= ================= Nikola Yes Pelican Yes Sphinx Yes Hugo If you install `docutils` Gatsby ?? Eleventy ?? Jekyll ?? Middleman ?? ========= =================
Overflow is okay, as long as the table markers themselves line up. Still. It's untidy. Excuse me a moment.
========= ========================= Generator Supports RST ========= ========================= Nikola Yes Pelican Yes Sphinx Yes Hugo If you install `docutils` Gatsby ?? Eleventy ?? Jekyll ?? Middleman ?? ========= =========================
|Hugo||If you install docutils|
Table construction can get more elaborate. Check out grid-table if that sort of thing interests you. It can also get simpler, with csv-table and table-listing directives.
Directives are used to extend RST. They're written in Python, but you don't need to understand Python to use them.
Directives share a basic structure:
.. directive-name:: arguments :option-name: option-values body
The details vary with every directive. Some require a body, some take no options. content generates a full table of contents without requiring arguments, options, or a body!
We've already looked at a couple directives. Do I have a favorite? Strangely enough, I do.
Most of this site's history has been me talking to myself. Sometimes I talk back. So I'm always looking for a good way to add assorted interjections and comments. Markdown doesn't officially support that sort of thing, so as a result my .md files have nonstandard components. Heck, for a while I had my own Hugo shortcode for this sort of thing.
Fortunately, these side notes are part of RST as admonitions.
.. note:: Don't forget to mention admonitions!
Don't forget to mention admonitions!
There are several admonition types, from the casual note to the dire alert.
.. warning:: Don't overuse admonitions!
Don't overuse admonitions!
note and warning should suffice for most cases.
This is mostly a coding blog. So of course I'm going to cover the code directive. You give it a language and some code. Pygments handles the highlighting. It handles nearly every language I have handed to it, so it should work nice.
How about a little snippet of Python from my circular grids post?
.. code:: python def main(): """Create a circle template from command line options""" # Get details from command line or use defaults parser = argparse.ArgumentParser() parser.add_argument("--size", help="length of image side in pixels", type=int, default=DEFAULT_SIZE) parser.add_argument("--circles", help="number of circles", type=int, default=DEFAULT_CIRCLES) parser.add_argument("--slices", help="number of slices", type=int, default=DEFAULT_SLICES) args = parser.parse_args() size = args.size circle_count = args.circles slice_count = args.slices circle_template = CircleTemplate(size, circle_count, slice_count) circle_template.save()
def main(): """Create a circle template from command line options""" # Get details from command line or use defaults parser = argparse.ArgumentParser() parser.add_argument("--size", help="length of image side in pixels", type=int, default=DEFAULT_SIZE) parser.add_argument("--circles", help="number of circles", type=int, default=DEFAULT_CIRCLES) parser.add_argument("--slices", help="number of slices", type=int, default=DEFAULT_SLICES) args = parser.parse_args() size = args.size circle_count = args.circles slice_count = args.slices circle_template = CircleTemplate(size, circle_count, slice_count) circle_template.save()
Oh my. I'm closing in on two thousand words. That's far more than I intended. Let's stop here, with the majority of my regular blog-writing needs covered.
Oh, fine. One little section at least.
Hand-drawing a table can be labor-intenstive — especially when you get fancy. Sometimes that is too much. Sometimes you just want to stuff values in a table.
csv-table serves that perfectly.
Let's say I have a CSV list of my most important Taskwarrior tasks for the site.
Hang on. How do I get a CSV list of tasks? Give me a second here.
$ task export project:Site status:pending priority:H | \ jq -r '. | [.id, .description, .urgency] | @csv' 227,"rst basics for blogging",11.9 228,"extract rst stylesheet",7.9
Perfect! Now where was I? Oh yes!
Let's say I have a CSV list of my most important Taskwarrior tasks for the site. I can paste that list under a csv-table directive, give it a caption and the header text — maybe set the widths option to auto, because I dislike the default of equal-width columns.
.. csv-table:: High priority site tasks :header: "ID", "Description", "Urgency" :widths: auto 227,"rst basics for blogging",11.9 228,"extract rst stylesheet",7.9
And it comes out not too bad!
|227||rst basics for blogging||11.9|
|228||extract rst stylesheet||7.9|
I feel bad. A two row CVS table does not save that much time. Maybe if I had 20 or 30 generated rows. And while it may be easier for stuffing values into a table, CSV is not the most readable format.
I can make it up to you. I just used list-table while switching a recent post to reStructuredText. It was a lifesaver.
.. list-table:: Emacs text scale adjustment key bindings :header-rows: 1 :widths: auto - - Function - Keys - Description - - ``(text-scale-adjust 1)`` - ``C-x C-=`` or ``C-x C-+`` - Increase text size by one step - - ``(text-scale-adjust -1)`` - ``C-x C--`` - Decrease text-size by one step - - ``(text-scale-adjust 0)`` - ``C-x C-0`` - Reset text size to default
Use nested lists to construct your list table. Each of the top list items represents a row in your table. Each of the items in a row list is a cell in that row. Because I specified :header-rows: 1, the first row gives use the table header.
|(text-scale-adjust 1)||C-x C-= or C-x C-+||Increase text size by one step|
|(text-scale-adjust -1)||C-x C--||Decrease text-size by one step|
|(text-scale-adjust 0)||C-x C-0||Reset text size to default|
I like this. Mind you, I get that simple and grid tables are easier to understanding when reading RST. There are fancy editor extension to draw simple or grid tables. Nevertheless, I'm writing this RST file with the intent of turning it into HTML. In that context — for me — pasting CSV or lines of text is easier than polishing text tables.
Okay I have got to stop now. Clearly I enjoy RST way too much.