HomeAbout MeSourceSitemapRSS

Current Build Process For This Site

Table of Contents

This website is built with org-mode and hosted through GitHub pages.

GitHub pages expects only a single index.html file and builds the site the from the docs directory - making the build process extremely simple.

Naturally, as this site is purely static there's no user tracking or even a requirement for https!

Build output

The build output will reside in docs to start we'll delete that directory and copy over the css files (that reside in resources/ and tell GitHub that this isn't a Jekyll site.

rm -rf docs
rm -rf src/tags-*.org # Tag files are stored in the format src/tags-TAG.org
mkdir docs
cp -r src/resources docs/
touch docs/.nojekyll  # For GitHub pages not to build a Jekyll site
echo -n "abdrysdale.phd" > docs/CNAME

Building the Home Page

The first thing I want on the home page is a list of articles with the article title as the description. I'd also like a list of tags that are used on the articles so the user can search by topic - topics are stored in the FILETAGS property. To do this, I create a cons list of (title . path) of each article in the src/ directory that isn't index.org.

First things first, let's define a generic function for extracting properties from an org-mode document.

(defun get-org-property (prop file)
  "Extract PROP from the org FILE."
  (when (file-exists-p file)
    (with-temp-buffer
      (insert-file-contents file)
      ;; No need for error handling here as (cdr nil) and (car nil) return nil
      (car (cdr (car (org-collect-keywords `(,prop))))))))

Next we define a function that returns all of the articles in format (file . link-title).

(defun get-titles-from-org-files (directory)
 "Extract titles from org files in DIRECTORY."
 (let ((files (directory-files directory t "^[[:alnum:]-_]+.org$")))
   (mapcar (lambda (file)
             (let ((rel-file (file-relative-name
                              file (expand-file-name directory))))
               `(,rel-file . ,(concat
                               (get-org-property "TITLE" file)
                               "\t"
                               (get-org-property "FILETAGS" file)))))
           files)))

Next the home page (index.org) is built. Currently this is really simple and just includes a template followed by a list of all of the articles and then a list of all the tags.

We'll take this template:

I'm Alex, a PhD candidate (haemodynamics + graph convolutional neural networks) and a trainee magnetic resonance physicist for NHS Wales.
Thank you for visiting my little patch of the internet.
All views are my own and not that of my employer.

You can expect content on programming, Emacs, philosophy, ethics, magnets and bread.
In addition to this, I'll try to post explanations of papers that I publish. If I'm not the first author I'll do my best to explain the paper or, more likely, explain my contribution to paper.

This is site is intentially minimal and left as an exercise to the reader...

* Copying

All material is in the website licensed under the the GNU/GPLv3 license - 
which can be found here.

insert a title and then add the articles and tags.

(defun build-index (author)
  "Build the index for the AUTHOR."
  ;; Copies the README.org to the index.
  (let ((dir "src")
        (index "src/index.org")
        (ignore-files '("index.org" "about.org" "sitemap.org"))
        (used-tags nil))

    ;; Inserts title and template
    (with-current-buffer (find-file-noselect index)
      (erase-buffer)
      (insert (format "#+title: %s\n\n" author))
      (insert-file-contents "../index-template.org")
      (end-of-buffer)
      (insert "\n\n* Articles\n")
      (save-buffer))

    ;; Gets all of the articles
    (dolist (result (get-titles-from-org-files dir))
      (let ((path (car result))
            (title (cdr result)))
        (with-current-buffer (find-file-noselect index)
          (goto-char (point-max))
          (unless (member path ignore-files)
            (let ((link (format "- [[file:%s][%s]]\n" path title))
                  (tags (get-org-property "FILETAGS" path)))
              (insert link) ;; Insert a link to article in the index

              ;; Insert a link to article in each of the tags file.
              (dolist (tag (split-string tags ":"))
                (unless (string-empty-p tag)
                  (let* ((tag-file (concat "tags-" tag ".org"))
                         (tag-entry `(,tag . ,tag-file)))
                    (unless (member tag-entry used-tags)
                      (push tag-entry used-tags))
                    (with-current-buffer (find-file-noselect tag-file)
                      (unless (file-exists-p tag-file)
                        (erase-buffer)
                        (insert (format "#+title:%s\n\n" tag)))
                      (insert link)
                      (save-buffer)))))
              (save-buffer))))))

    ;; Insert a link to the tag files in the index
    (with-current-buffer (find-file-noselect index)
      (insert "\n* Tags\n\n")
      (dolist (tag-info used-tags)
        (let ((tag (car tag-info))
              (file (cdr tag-info)))
          (insert (format "- [[file:%s][%s]]\n" file tag)))))))

Add an RSS feed

(defun get-rss-feed-item (title link)
  "Return an rss feed item with TITLE and LINK."
  (concat
   "<item>\n"
   "<title>" title "</title>\n"
   "<link>" link "</link>\n"
   "</item>\n"))
(defun build-rss-feed (title link desc src out)
  "Build a rss feed for TITLE (DESC) at LINK using the posts in SRC to OUT."
  (with-current-buffer (find-file-noselect (concat out "feed.xml"))
    (erase-buffer)
    (insert (concat
             "<rss version=\"2.0\">\n"
             "<channel>\n"
             "<title>" title "</title>\n"
             "<description>" desc "</description>\n"
             "<link>" link "</link>\n"))
    (dolist (file (directory-files src nil "^[[:alnum:]-_]+.org$"))
      (insert (get-rss-feed-item (get-org-property "TITLE"
                                                   (concat src "/" file))
                                 (concat link "/"
                                         (car (split-string file ".org"))
                                         ".html"))))
    (insert "</channel>\n</rss>")
    (save-buffer)))

Publishing the Site

Finally, the site is published using ox-publish with this article (the README.org) being copied as an article.

One thing of note is that we always publish the articles under the same theme for continuity.

(require 'ox-publish)
(require 'whitespace)
(require 'htmlize)
(let ((current-theme (if custom-enabled-themes
                         (car custom-enabled-themes)
                       'modus-operandi))
      (publish-theme 'modus-operandi)
      (whitespace-style nil)
      (whitespace-mode 0)
      (org-html-validation-link nil)
      (org-html-head-include-scripts nil)
      (org-html-head-include-default-style nil)
      (org-html-head (concat
                      "<link rel=\"stylesheet\""
                      "href=\"resources/org.css\""
                      "type=\"text/css\" />"
                      "<header>"
                      "<a href=\"index.html\">Home</a>"
                      "&emsp;<a href=\"about.html\">About Me</a>"
                      "&emsp;<a href=\"https://github.com/abdrysdale/abdrysdale.github.io\">Source</a>"
                      "&emsp;<a href=\"sitemap.html\">Sitemap</a>"
                      "&emsp;<a href=\"feed.xml\">RSS</a>"
                      "</header>\n"))
      (org-src-fontify-natively t)
      (org-publish-project-alist
       '(("blog"
          :base-directory "src"
          :recursive t
          :publishing-directory "docs"
          :auto-sitemap t
          :recursive t
          :with-author nil
          :with-creator t
          :with-toc t
          :headline-levels 1
          :section-numbers nil
          :time-stamp-file nil
          :publishing-function org-html-publish-to-html))))
  (copy-file "README.org" "src/colophon.org" t)
  (build-index "Alex Drysdale")
  (build-rss-feed
   "Alex Drysdale"
   "https://abdrysdale.phd"
   "Blog posts by Alex Drysdale"
   "../src/" "docs/")
  (load-theme publish-theme)
  (org-publish-all t)
  (load-theme current-theme)
  (message "Site built at %s"
           (format-time-string "%Y-%m-%d %H:%M:%S")))

Git Hooks

This script is tangled into .git/hooks/build.el which means that we just need to create a pre-commit hook that runs the build.el file.

#!/bin/sh
emacs --batch -Q --script build.el
git add docs/*.html

and make that file executable:

chmod +x .git/hooks/pre-commit

Conclusion

Not the most beautiful blog, or the most elegant build solution but this allows me to just write without think about much each.

There's still a few things I'd like to implement in the build process namely:

Date: 2025-04-19 Sat 00:00

Emacs 29.4 (Org mode 9.6.15)