Custom org-sitemap-function post-Org 9.1

Published: December 27, 2022

I have been lugging around an old version of org-mode (9.0 to be specific) in the git repo which builds this website for a number of years now. I decided to do this because I had a custom org-sitemap-function to generate the landing page for my blog, but org 9.1 introduced a breaking change to the org-publish API.

I have now finally come around to fixing this issue and making my website compatible with modern emacs and org-mode versions higher than 9.1. However, porting my old sitemap function was… surprisingly difficult? So just in case someone is looking up my original post these days, this post contains a sitemap-function which will work in 2022.

In the pre-9.1 version, the org-sitemap-function would get the project-plist as argument. Post-9.1, the org manual states:

:sitemap-function

Plug-in function to use for generation of the sitemap. It is called with two arguments: the title of the site-map and a representation of the files and directories involved in the project as a nested list, which can further be transformed using org-list-to-generic, org-list-to-subtree and alike. Default value generates a plain list of links to all files in the project.

This makes it a little more difficult to produce the sitemap page that we want, but I was able to get it done by parsing each element of the list and extracting the path to the filename with the following function:

(defun my-blog-parse-sitemap-list (l)
  "Convert the sitemap list in to a list of filenames."
  (mapcar #'(lambda (i)
              (let ((link (with-temp-buffer
                            (let ((org-inhibit-startup nil))
                              (insert (car i))
                              (org-mode)
                              (goto-char (point-min))
                              (org-element-link-parser)))))
                (when link
                  (plist-get (cadr link) :path))))
          (cdr l)))

Finally, the new and improved sitemap function looks like this:

(defun my-blog-sort-article-list (l p)
  "sort the article list anti-chronologically."
  (sort l #'(lambda (a b)
              (let ((d-a (org-publish-find-date a p))
                    (d-b (org-publish-find-date b p)))
                (not (time-less-p d-a d-b))))))

(defun my-blog-sitemap (title list)
"Generate the landing page for my blog."
(with-temp-buffer
  ;; mangle the parsed list given to us into a plain lisp list of files
  (let* ((filenames (my-blog-parse-sitemap-list list))
         (project-plist (assoc "blog-articles" org-publish-project-alist))
         (articles (my-blog-sort-article-list filenames project-plist)))
    (dolist (file filenames)
            (let* ((abspath (file-name-concat my-website-blog-dir file))
                   (relpath (file-relative-name abspath my-website-base-dir))
                   (title (org-publish-find-title file project-plist))
                   (date (format-time-string (car org-time-stamp-formats) (org-publish-find-date file project-plist)))
                   (preview (my-blog-get-preview abspath)))
              ;; insert a horizontal line before every post, kill the first one
              ;; before saving
              (insert "-----\n")
              (insert (concat "* [[file:" relpath "][" title "]]\n"))
            ;; add properties for `ox-rss.el' here
            (let ((rss-permalink (concat (file-name-sans-extension relpath) ".html"))
                  (rss-pubdate date))
              (org-set-property "RSS_PERMALINK" rss-permalink)
              (org-set-property "PUBDATE" rss-pubdate))
            ;; insert the date, preview, & read more link
            (insert (concat "Published: " date "\n\n"))
            (insert preview)
            (insert "\n")
            (insert (concat "[[file:" relpath "][Read More...]]\n"))))
    ;; kill the first hrule to make this look OK
    (goto-char (point-min))
    (let ((kill-whole-line t)) (kill-line))
    ;; insert a title and save
    (insert "#+OPTIONS: title:nil\n")
    (insert "#+TITLE: Blog - Dennis Ogbe's Personal Website\n")
    (insert "#+AUTHOR: Dennis Ogbe\n")
    (insert "#+EMAIL: [email protected]\n")
    (buffer-string))))

This info can be combined with the instructions in my original post to cook up your own very special org-mode website.

Update December 29, 2022: I realized the info in the old post is very outdated. So here goes the current (late 2022) version of my website build script. I usually call this using emacs --batch, i.e., something like:

emacs --batch -l "./project.el" --eval="(org-publish \"blog\" t)"

As part of the build process, I use CSSTidy to minify my CSS and bibtex2html to generate the list of publications.

;; [2024-12-30 Mon]
;;
;; I used to host the current version of the build script here. That made this
;; post rather large and hard to parse. Please find a more recent annotated
;; version of my build script here: https://ogbe.net//blog/emacs_org_static_site
;;
;; Also see the update note at the bottom of this post.

Update January 02, 2023: I changed a few minor things, including that I now finally add the publish date into the actual published blog post. Also, thanks to a hint by G.M., who provided a fix for my old structure-template definition, I now have an updated structure template to generate the header for a blog post (see here for an explanation):

(require 'org-tempo)
(tempo-define-template "blog-header" ; just some name for the template
                       '("#+title: ?" n
                         "#+AUTHOR: Dennis Ogbe" n
                         "#+EMAIL: [email protected]" n
                         "#+DATE:" n
                         "#+STARTUP: showall" n
                         "#+STARTUP: inlineimages" n
                         "#+BEGIN_PREVIEW" n p n
                         "#+END_PREVIEW")
                       "<b"
                       "Insert blog header" ; documentation
                       'org-tempo-tags)

Update December 30, 2024: After some updates to my generator, I now have a writeup with commentary here.