Simplifying My Blog

Published Friday, October 7 2022

Note: This blog post is no longer accurate, a lot has changed since it was published. It is left here for historical reasons.

Over the last couple of days I’ve been busy ripping out a lot of the Emacs lisp code that I orignally wrote to publish my weblog with Emacs and cleaning up the project quite a bit. The most obvious changes are that there’s finally an RSS feed again, and that my weblog is all on one page now instead of being spread out across multiple pages with “Next Page” and “Previous Page” links. You might reasonably be asking why I would do such a thing, and it all comes down to complexity and brittleness. I don’t like complexity or brittleness!

The complexity came from the hackish code I wrote to split my weblog into multiple pages. A lot of the Emacs publishing pipeline is highly customizable and offers you the option to write your own functions to override default behavior, but there’s no way to tell it to use a custom function to change how the blog index gets written to a file (or files). So, until this most recent change, I was actually un-defining one of the core internal publishing functions and replacing it with my own definition.

Of course, doing it that way touched a lot of the internal and private API, and that’s really a no-no. Yes, it worked, but it was difficult to maintain, and prone to breakage.

So I gave up on that. Instead I’m going with the flow now and doing things more in line with how Emacs publishing does them by default, and that means the whole blog index on one page. I may eventually take it upon myself to offer org-mode a patch to do support custom multi-page stuff at a later date, but for now, this is the simplest and cleanest path forward.

A Little More Detail

The traditional way to publish a weblog with org-mode is to create an org-publish project with an auto sitemap. The default sitemap is really just a list of weblog post titles that you can click on to visit the linked page. It’s very bare bones and no nonsense, and normally I can get behind that kind of thing (Keep It Simple, Stupid!), but I felt like it was actually too bare-bones. I went the extra mile and wrote some Emacs lisp to put either a preview or the whole blog post right into the index page for the sitemap. Luckily, that’s pretty easy to do with org-mode.

(N.B.: The source code examples in this post come from my Loomcom Homepage project on GitHub. If you want to see the latest version, you can always find it there. The snippets in this post may or may not have changed by the time you read this post!)

The first bit of code I added is a function that grabs the part of the post that I want to include in the sitemap index page. This will return the content starting just below the post headers, down to any #+MORE_LINK: keyword or the end of the post, whichever comes first.

(defun loomcom/get-preview (entry)
  "Get the preview of a blog entry, and whether a `read more'
link is required to see the entire post. This is returned as a
cons pair of `(needs-more . preview-text)'. ENTRY is the entry to
get the preview for."
    (insert-file-contents (concat loomcom/blog-org-dir entry))
    (goto-char (point-min))
    (let* ((len (buffer-size))
           (content-start (or
                           ;; Look for the first non-keyword line
                           (and (re-search-forward "^[^#]" nil t)
                                (match-beginning 0))
                           ;; Failing that, assume we're malformed and have no conent.
           (marker (or
                    (and (re-search-forward "^#\\+MORE_LINK:" nil t)
                         (match-beginning 0))
           (needs-more (not (= marker len)))
           (preview-text (string-trim (buffer-substring content-start marker))))
      (cons needs-more preview-text))))

Next I defined a function to use when formatting entries in the sitemap. It writes out the preview followed by a “Read More” link that will take you to the entry’s HTML page. If a “Read More” link is not needed, it’ll just dump the whole entry into the sitemap.

(defun loomcom/blog-sitemap-entry (entry style project)
  "Default format for site map ENTRY, as a string.
ENTRY is a file name.  STYLE is the style of the sitemap.
PROJECT is the current project."
  (cond ((not (directory-name-p entry))
         (let* ((file (concat loomcom/blog-org-dir entry))
                (title (org-publish-find-title entry project))
                (date (org-publish-find-date entry project))
                (preview (loomcom/get-preview entry))
                (needs-more (car preview))
                (preview-text (cdr preview)))
           (format (concat
                    "* [[file:%s][%s]]\n"
                   (format-time-string "%A, %B %_d, %Y at %l:%M %p %Z" date)
                   (if needs-more
                         "[[file:%s][Read More →]]\n"
                        preview-text entry)
        ((eq style 'tree)
         ;; Return only last subdir.
         (file-name-nondirectory (directory-file-name entry)))
        (t entry)))

And last but not least, there’s a function that describes how to format the index page.

(defun loomcom/sitemap (title list)
  "Generate blog sitemap, as a string. TITLE is the title of the
blog. LIST is an internal representation of the files to include,
as returned by `org-list-to-lisp'. PROJECT is the current
  (concat "#+TITLE: " title "\n\n"
          (org-list-to-subtree list nil '(:icount "" :istart ""))))

These functions are then used in the project’s configuration in the org-publish-project-alist:

(setq org-publish-project-alist
         ;; ... more configuration ...
         :auto-sitemap t
         :sitemap-style list
         :sitemap-format-entry loomcom/blog-sitemap-entry
         :sitemap-function loomcom/sitemap
         :sitemap-filename ""
         :sitemap-title "Seth Morabito ∴ A Weblog"
         :sitemap-sort-files anti-chronologically)

        ;; ... more project definitions ...


When it all comes together, you get the blog you’re reading right now. It works for me, and I was able to re-use a lot of it to generate the RSS feed, a feature I kind of missed even though almost nobody uses RSS anymore (hey, what do you say to an RSS renaissance?)