I finally decided to figure out the workflow I would use for writing blog articles, and I ended up going with a static site generator. I checked out the python options, and pelican seemed to be the clear frontrunner for me. It uses jinja2 which I already know, and the pelican docs explain everything you could want to know.

Once configured properly.. you can regenerate your html/css files from markdown - and then publish them in one of 2 ways:

fab build && fab publish
# or
make rsync_upload

I went with fabric here just because it's easier for me to customize, and 'cause python. Though the make solution works quite well. Both make use of rsync's --checksum flag which transfers new files based on content, rather than modification time - this is nice because it regenerates every file every time you build which would require an entire site transfer if it weren't for this flag.

My main goal was to be able to publish an article from org-mode with one command, in this case I need to make some elisp which does the following:

  • Extracts which articles are publishable from my org-mode file
  • Exports them to the proper locations as markdown files
  • Calls fabric to regenerate and publish

To show how my website is structured in my org-mode document, here's a layout:

* danlamanna.com
** pages
*** cv
*** contact
** posts
*** blogging-with-org-mode-pelican-fabric-and-elisp             :incomplete:
*** flock-all-of-the-cron-jobs
*** ...

Since my org file has many headings over than the above, we need to define some constants which will help our later defined functions:

(defconst org-pelican-site-dir "~/files/etc/danlamanna.com")
(defconst org-pelican-org-heading "danlamanna.com")
(defconst org-pelican-publish-level 3) ;; 3 asterisks in front of everything that needs to be published

Sometimes I work on a post for a couple of days, or I just create a bullet point for an idea - but I don't want it to be published yet, for these cases I'll tag an org entry as incomplete, so let's create a small function which determines if an entry can be published:

(defun org-pelican-publishable-p()
  (not (-contains? (org-get-tags) "incomplete")))

Finally, the emacs-lisp that runs it all:

(defun org-pelican-publish()
  (interactive)
  (save-excursion
    (goto-char (org-find-exact-headline-in-buffer org-pelican-org-heading (current-buffer) t))
    (org-narrow-to-subtree)
    (org-content org-pelican-publish-level)
    (while (< (point) (point-max))
      (if (and (org-pelican-publishable-p)
               (eq (nth 1 (org-heading-components)) org-pelican-publish-level))
          (org-md-export-to-markdown nil t))
      (outline-next-visible-heading 1))
    (widen))
  (with-temp-buffer
    (cd org-pelican-site-dir)
    (shell-command "fab build && fab publish")))

To break it down in english, the function:

  • Finds the headline of the website element, and narrows the entire buffer to just this subtree
  • Expands all org entries to level 3, so we can see our posts/pages
  • Loops until we've reached the end of our subtree
    • Checks if the entry is publishable with our earlier function, and that it's on level 3
    • It exports the entry to markdown if it's publishable, otherwise it skips to the next visible org entry
  • Expands our subtree view back to the original file
  • Switches to the directory of our pelican install, and runs fabric to build and publish the site

And.. to publish this blog post, I'll just M-x org-pelican-publish