Eric Evenchick

Blogging with Org Mode

I am now using emacs org-mode to generate this website.

I've been using org-mode on a daily basis for note-taking and keeping track of todo lists for a while now, and have come to prefer the syntax of org files over Markdown and reStructuredText. I'm not sure that org is better, it's just the format I know best.

I have used a few static site generators in the past, but always ended up not touching them for a while. After that, I'd have to relearn how they worked any time I wanted to publish anything. I saw some other examples of folks using org-mode to publish sites (notably, this example) and decided to give it a try for myself.

Static web sites are handy for personal sites since they're cheap and easy to run. Fortunately, org-mode has a decent static site generator built in. All the configuration for the site lives in my emacs configuration. By using M-x org-publish-site, I can publish the site at any time. When working with source code, org-mode does a good job of applying syntax highlighting to exports. Other emacs tools such as flyspell-mode and writegood-mode make writing in emacs much nicer.

Lets look at how this configuration works. First, we define a couple of helper functions. The first loads an HTML file into a buffer then converts the buffer to a string. The function loads HTML templates for the header and footer:

(defun add-html-file (arg)
    (with-temp-buffer
    (insert-file-contents arg)
    (buffer-string)))

We will also define a custom helper function for formatting items in the articles list:

(defun my-site-format-entry (entry style project)
    (format "[[file:%s][%s]] --- %s"
            entry
            (org-publish-find-title entry project)
            (format-time-string "%Y-%m-%d" (org-publish-find-date entry project))))

Lets set a variable for the path to the site, and another for the output path:

(setq my-site-project-path "/home/eric/Dropbox/blog/")
(setq my-site-publish-path (concat my-site-project-path "_build/")

Set variables for our custom HTML. We use the helper function to grab the header and footer HTML:

(setq my-site-extra-head "<link rel='stylesheet' href='/static/main.css' />")
(setq my-site-header-file (concat my-site-project-path "templates/header.html"))
(setq my-site-footer-file (concat my-site-project-path "templates/footer.html"))

Now we're ready to configure org-publish. This all happens in the project list. We set a top-level project called site which will generate all of the sub-projects. The site-static, site-images, and site-dl projects all publish assets by copying them to the output directory. The site-pages project publishes the site index, and site-articles publishes articles for the blog as HTML.

To generate an index of articles, we use the auto-sitemap feature. This makes use of our my-site-format-entry helper function.

(setq org-publish-project-alist
`(("site"
   :components ("site-static", "site-pages", "site-images", "site-articles", "site-dl"))
  ("site-static"
   :base-directory ,(concat my-site-project-path "static/")
   :base-extension ".*"
   :publishing-directory ,(concat my-site-publish-path "static/")
   :publishing-function org-publish-attachment
   :recursive t)

  ("site-images"
   :base-directory ,(concat my-site-project-path "img")
   :base-extension ".*"
   :publishing-directory ,(concat my-site-publish-path "img/")
   :publishing-function org-publish-attachment
   :recursive t)

  ("site-dl"
   :base-directory ,(concat my-site-project-path "dl")
   :base-extension ".*"
   :publishing-directory ,(concat my-site-publish-path "dl/")
   :publishing-function org-publish-attachment
   :recursive t)

  ("site-pages"
   :base-directory ,(concat my-site-project-path "pages/")
   :base-extension "org"
   :publishing-directory ,my-site-publish-path

   :html-link-home "/"
   :html-head nil
   :html-head-extra ,my-site-extra-head
   :html-head-include-default-style nil
   :html-head-include-scripts nil
   :html-home/up-format ""

   :html-preamble ,(add-html-file my-site-header-file)
   :html-postamble ,(add-html-file my-site-footer-file)

   :makeindex nil
   :with-toc nil
   :section-numbers nil

   :publishing-function org-html-publish-to-html)

  ("site-articles"
   :base-directory ,(concat my-site-project-path "articles/")
   :base-extension "org"
   :publishing-directory ,(concat my-site-publish-path "blog/")

   :html-link-home "/"
   :html-head nil
   :html-head-extra ,my-site-extra-head
   :html-head-include-default-style nil
   :html-head-include-scripts nil
   :html-home/up-format ""

   :html-preamble ,(add-html-file my-site-header-file)
   :html-postamble ,(add-html-file my-site-footer-file)

   :makeindex nil
   :auto-sitemap t
   :sitemap-filename "index.org"
   :sitemap-title "Articles"
   :sitemap-style list
   :sitemap-sort-files anti-chronologically
   :sitemap-format-entry my-site-format-entry 
   :with-toc nil
   :section-numbers nil

   :publishing-function org-html-publish-to-html
   :recursive t))))

Any server could host the content created by org-publish to create a static site. I'm using Amazon AWS, specifically S3 and CloudFront. S3 hosts the content, and CloudFront provides secure access over HTTPS. Yes, even static sites should use HTTPS!

The final result is a site that should be easy to keep up-to-date. At the least, I hope to publish content from talks here going forward.