StrangeStack

Lispegistus' vault of Hacks and Glory


Blogging With Org-Mode

New Blog. Hacks and Glory Await!

I spent the entire day battling Emacs until I got it to spit out this blog. I'm not gonna say it wasn't fun, but it was definitely harder than I expected. Not to say that Emacs' org-mode isn't a perfectly fine way to generate HTML, it's just that getting the details like I want them turned out to be much harder. And after an entire day of googling, I got the following:

structure

The source directory where my static assets are held is /home/pav/strangestack/strangestack.com_source/. Inside it I have the follewing:

  • posts/ directory where blog posts are held
  • pages/ directory where the static pages are held
  • static/ directory where the static assets are held
  • strangestack.setup is my setup file.

Setup file

The setup file contains a bunch of options common to all my pages, such as links in the <head> element and some metadata among many other options:

# -*- mode: org; -*-

#+AUTHOR: Pavel Penev
#+EMAIL: lispegistus@strangestack.com
#+LANGUAGE: en
#+OPTIONS: h:4
#+OPTIONS: toc:nil
#+options: html-link-use-abs-url:t html-postamble:t html-preamble:t html-scripts:t html-style:nil html5-fancy:t
#+options: tex:t num:nil
#+html_doctype: html5
#+html_container: div
#+description:
#+keywords:
#+html_mathjax:
#+html_head: <link rel="stylesheet" href="static/main.css"> 
#+html_head: <link rel="me" href="https://hachyderm.io/@lispegistus">
#+html_head: <link rel="icon" type="image/png" href="static/favicon.png">
#+html_head_extra:
#+infojs_opt:
#+creator: <a href="https://www.gnu.org/software/emacs/">Emacs</a> 27.1 (<a href="https://orgmode.org">Org</a> mode 9.3)
#+latex_header:

Now I simply have to put #+SETUPFILE: /home/pav/strangestack/strangestack.com_source/strangestack.setup at the top of any org file in my project.

Emacs config

In my .emacs file, the configuration is fairly simple. First we require ox-publish and ox-rss and define our preamble and postamble templates. These will be included in each page automatically. The preamble includes my title and navigation and the postamble is my footer information:

(require 'ox-publish)
(require 'ox-rss)

(setq org-html-preamble-format
      '(("en" "
<a href=\"index.html\"><h1 class=\"maintitle\"> StrangeStack </h1></a>
<h3> Lispegistus' vault of Hacks and Glory <h3>
<nav class=\"nav\">
<a class=\"navel\" href=\"about.html\">About</a> |
<a class=\"navel\" href=\"blog.html\">Blog</a> |
<a class=\"navel\" href=\"https://code.strangestack.com/explore/repos\">Code</a>
</nav>

<hr>
")))
(setq org-html-postamble-format
      '(("en" "
<hr>
<p class=\"author\">Author: %a (%e)</p>
<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png\" /></a> Unless otherwise specified, everything on this site is licensed under a <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>.
<a href=\"https://strangestack.com/rss.xml\">
<br>
<img src=\"static/rss.gif\" width=\"36\" height=\"14\"> Subscribe to the Blog.
</a>
<p class=\"creator\">Generated by: %c</p><br>")))

Since this is a blog, the simplest way to create one is to have a separate directory for the blog posts and use org's sitemap functionality generate an index of our blog posts. The way it works is org will scan a directory and build a sitemap org file that will then get exported to HTML just like it was a regular part of the site. Telling org to include our setup file is as simple as defining the a function:

(defun sitemap-function (title list)
  (concat
   "#+SETUPFILE: /home/pav/strangestack/strangestack.com_source/strangestack.setup\n"
   "#+TITLE: " title "\n\n"
          (org-list-to-subtree list '(:icount "" :istart ""))))

Now we tell org where all of our files are and how to export them. We do this by setting the org-publish-project-alist. It's a list and every item in the list is a project definition. We'll have 3:

  • posts will export our blog posts and generate a sitemap called blog.org which will serve as the index the blog
  • pages will export our static pages
  • static will export our static assets

Let's look at the code, first it's for the static. It basically recirsively copies everything in the static directory:

("static"
 :base-directory "/home/pav/strangestack/strangestack.com_source/static/"
 :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
 :publishing-directory "/home/pav/strangestack/strangestack.com/static/"
 :recursive t
 :publishing-function org-publish-attachment
 )

The pages are just as simple:

("pages"
 :base-directory "/home/pav/strangestack/strangestack.com_source/pages/"
 :base-extension "org"
 :publishing-directory "/home/pav/strangestack/strangestack.com/"
 :recursive t
 :publishing-function org-html-publish-to-html
 :headline-levels 4
 :auto-preamble t
 :auto-postamble t)

Each org file in the pages directory will be exported with a preamble and postamble using the standard org-html-publish-to-html function.

The blog is more complicated:

("posts"
 :base-directory "/home/pav/strangestack/strangestack.com_source/posts/"
 :base-extension "org"
 :publishing-directory "/home/pav/strangestack/strangestack.com/"
 :recursive nil
 :exclude  "\\(?:\\(?:404\\|blog\\|rss\\)\\.org\\)"
 :publishing-function org-html-publish-to-html
 :headline-levels 4                     ; Just the default for this project.
 :auto-preamble t
 :auto-postamble t
 :auto-sitemap t
 :sitemap-title "StrangeStack.com Blog Posts"
 :sitemap-sort-files anti-chronologically
 :sitemap-function
 (lambda (title list)
   (concat
    "#+SETUPFILE: /home/pav/strangestack/strangestack.com_source/strangestack.setup\n"
    "#+TITLE: " title "\n\n"
    (org-list-to-org list )))
 :sitemap-format-entry
 (lambda (entry style project)
   (let* ((title (org-publish-find-title entry project))
          (date (format-time-string "%d %B %Y" (org-publish-find-date entry project)))
          (subtitle (car (org-publish-find-property entry :subtitle project 'html))))
     (if subtitle
         (format "[[file:%s][%s]]\n\n%s\n\nPublished on: %s" entry title subtitle date)
         (format "[[file:%s][%s]]\n\nPublished on: %s" entry title date))
     ))
 :sitemap-filename "blog.org")

We do a few things here in addition to simply exporting all the post files, we're building a site map. We want the sitemap to be in reverse chronological order and we use the sitemap-function and sitemap-format-entry options to do a few things. First we want the sitemap file to include our setup file, and we want each entry to be a list item of the title of the blog post and the subtitle under it, if it's present, plus a publish date at the end. All of this gets dumped into the blog.org file and it gets exported as normal.

And we have our blog. from emacs we just call the org-publish-all command and our site will appear in the output directory. I have a few more things in my .emacs file that affect the output, such as the theme I'm using, org will attempt to highlight code blocks according to the current active theme, which in my case is solarized-dark, after which I've styled the entire website.

Conclusion

As a first iteration at using emacs to generate a static site, I think I'm happy with this setup for now. I'll continue to fiddle with it in the future of course, but in general I like how it turned out.


Author: Pavel Penev (lispegistus@strangestack.com)

Creative Commons License Unless otherwise specified, everything on this site is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Subscribe to the Blog.

Generated by: Emacs 27.1 (Org mode 9.3)