Pelican is a static site generator. Out of the box it supports two formats: Restructured Text and Markdown. There exist plugins to support the Org format. For a while, I would write my posts in org mode, with one file per post. But I longed for a way to have all my posts in one org file, with each post being in its own node. Something like ox-hugo for Hugo.

So I finally wrote one about a year ago. It is my first significant foray into Elisp. It is also my first significant piece of code I wrote using Org’s literate programming capabilities.

My original goal had been to get this into MELPA, but in the year since I wrote it I’ve not really had time to clean up the code, and given these trends I doubt I’ll spend time maintaining it if bugs are found. So I’ve decided to post the code here in case anyone else wants to use it. I would not recommend anyone rely on it unless they are willing to debug the occasional problem that crops up. It’s very useful, but it’s not perfect.

The literate version is here.

Motivation

So why would one want this anyway? What’s wrong with doing one post per file?

There are some advantages to this:

  • You can easily add images to your post and view them within Emacs.
  • It is much easier to internally link between posts. With one post per file you would have to know the linked post’s filename.
  • You can write equations and view them in Emacs.
  • Organization (the “org” in Org mode). You are free to structure your posts any way you wish. As an example, if I’m writing a series of posts on how flux capacitors work, I can put all the posts under a heading called Flux Capacitors.
  • Along the same lines, you can utilize tag inheritance. To tag all my flux capacitor posts with the tag metaphysics, I simply tag the top level node. This is more convenient than it sounds.
  • You can have extra information/text in your master org file that will not be exported.
  • Sometimes I’m browsing old posts of mine and I find a typo. I now simply search for that typo in one file and fix it, as opposed to figuring out which file corresponded with which post and opening that and fixing it. This is also more convenient than it sounds.

Screenshot

Below is a screenshot of my master org file.

Screenshot

Prerequisites

You need to have Pandoc installed.

How It Works

Briefly:

org-pelican will examine every headline to see if it is a post or not. If it is, it will create a temporary org file with the contents of the post, along with any metadata (date, status, etc). It will then use pandoc to create a rst file from this org file. Pelican then takes care of the rest.

The rst files will be created in a user specified directory. Do not store anything else in this directory. org-pelican will delete anything stored in it.

Variables

You need to define the following Emacs variables:

  • opel-tmp-file: This is the name of the org file that will contain your post. I use /tmp/file.org for this.
    • Note that org-pelican will not delete this file - it will contain the contents of the last post it processed.
  • opel-output-dir-name: This should be the name of the directory under content/pages and content/articles that will contain all the org-pelican generated rst posts. I recommend you not version control this, and do not put anything in this directory as it is wiped out by opel. I usually set this variable to orgpelican.
  • opel-page-dir: The full path for the above directory for pages. So /home/janedoe/blog/content/pages/orgpelican.
  • opel-article-dir: Like the above, but for articles.
  • opel-no-export: This is the tag you will use to denote that a heading should not be exported. I use noexport.

Yes, I realize there is redundancy in the above variables.

Since I have multiple Pelican sites, I make these variables file local by adding this to the top of my Org file.

-*- mode: org; opel-tmp-file: "/tmp/file.org"; opel-output-dir-name: "orgpelican"; opel-page-dir: "/home/janedoe/pelican/blog/content/pages/orgpelican"; opel-article-dir: "/home/janedoe/pelican/blog/content/articles/orgpelican"; -*-

WARNING!

Be very careful with the value you provide to opel-output-dir-name! org-pelican will delete anything it finds in that directory.

Creating a Post

Make a node anywhere in your master org document with the following properties:

:PELICAN_DATE: [2018-09-29]
:PELICAN_TYPE: article

The date should be what Org mode accepts as a date (active or inactive). So keep the brackets.

PELICAN_TYPE should be either article or page.

Please ensure children of this node do not set these properties. I have no idea what happens if you do.

The headline of your node will be the title of the post.

Status of a Post

A post can be either DRAFT, HIDDEN or PUBLISHED. To specify this for a given node, put this near the top of your org file:

#+TODO: DRAFT | HIDDEN PUBLISHED

To mark a node as DRAFT, simply give it a TODO property of DRAFT.

I believe that if you do not put a TODO property on a node it will be DRAFT by default. I think it’s good style to explicitly mark it as DRAFT though so it stands out.

Tags

To tag a post, simply add a tag to the node. If you want your tag to have a space, then in the Org document use underscore. So this_tag will get converted to this tag in Pelican.

The corollary is that you can’t really have tags with underscores.

Also, please do not use commas in your tags. They are used as delimiters in the rst file.

Internally Linking to Another Post

Link it just as you would within an org document. Ensure the target node has a CUSTOM_ID property and use the usual org internal linking capabilities.

Images

You can link to images just as you would in org mode, but with restrictions:

  1. Do not supply a description for the link. This is purely an inline link.
  2. The master org file is in the parent directory of images under content.
  3. The images directory is where all the images go, and is in the STATIC_PATHS variable in the pelicanconf.py file. You can organize them as you wish in subdirectories.

At least, these were the restrictions when I first wrote the code. I assume they are still valid.

Basically, what I think it is doing behind the scenes, is converting:

#+width: 200px
#+CAPTION: Your alt text
\[\[file:images/path/image.png]]

(Ignore the backslashes above. Org-pelican is not smart enough to realize the above is in a literal block).

#+BEGIN_EXPORT rst
.. image:: {static}/images/path/image.png
   :target: {static}/images/path/image.png
   :alt: Your alt text
   :width: 200px
#+END_EXPORT

I don’t know if it will work if you do not have the CAPTION, or if your caption exceeds one line. You do not need to have the width attribute.

Internally Linking to Static Files

I don’t have a great way to link to static files (other than images). I probably should fix this some day. The only way at the moment is to make a link like:

\[\[file:{static}/path/to/file.ext][Description]]

(Do not include the backslashes, obviously).

This works, but has the down side that the link will not work within org mode.

LaTeX Formulae

Most LaTeX formulae will work if you have the rendermath plugin.

However, if you want to use newcommand to define your own LaTeX commands, make sure you put lines like

#+LATEX_HEADER: \newcommand{..}{..}

near the top of your org file, and not in your node. This way you can utilize the new commands within your org file, allowing you to create previews, etc while you type.

Now the down side of this: You will have newcommnd defined in all the HTML of all your posts. There is no way to specify that they appear only in the posts that have math in them.

The other problem is that they will appear in all your summaries. So your main page will have nothing but newcommand summaries. My solution is to update my template and put them in the head part of the HTML document:

{% if article.newcommands %}
     <p style="display:none">{{ article.newcommands }}</p>
{% endif %}

So you’ll need to update your template for them to work.

Prevent Exporting of a Post

If you have a post written, but for some reason want to “comment” it out, tag it with whatever the contents of opel-no-export are. This will comment out the whole subtree (assuming tag inheritance is enabled).

I do not recall what happens if your main node is a post (that you intend to export), but a child node has this tag. Will it simply not export the child node but will create a post? I hope so, but I do not remember.

Do keep in mind that if you want to have plenty of nodes in your org document that are not posts, you do not need to use this feature. Simply ensure your node is not a child of a post and it will not be processed by org-pelican.

Exporting to Pelican

To export all the posts, run M-x opel-export in the master org file.