Using a Blog post as build instructions

Table of Contents

I will be continually updating and editing this blog post as I grow this website so consider this to be a small forever post.

The why

I had an interesting thought as I was switching my website away from using hugo as the static site generator to using org-publish. Why not try and use the literal programming feature of Org to have the actual build instructions for org-publish as an actual blog post.

Now why org-publish?

Well because org mode is the only decent way of writing documents without going insane.

The real reason is that one of the many fantastic things about org mode is the ability to write blocks of code in many different languages, and execute them all in the same session, a superior Jupyter notebook but for all languages. This comes in handy when you are engaging in exploratory work which is often what I do. Therefore, compiling it into a blog post becomes the natural next step and I just have to move the file over and walla its there, ready to be published.

This specific site uses this blog post, a shell script and github pages to actually build and host the website.

I do have a few other files that I need to move over such as assets, the preamble and postamble to this blog post

Packages

Set the packages into a hidden folder

(require 'package)
(setq package-user-dir (expand-file-name "./.packages"))
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("elpa" . "https://elpa.gnu.org/packages/")))

Initialise the package system

(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))

Install dependencies

(package-install 'htmlize)

Load the publishing system

(require 'ox-publish)

Setting the stylesheets

To set the css style sheets in the HTML-HEAD.

(setq set-css "<link rel=\"stylesheet\" type=\"text/css\" href=\"/asset/css/style.css\"/><link rel=\"stylesheet\" href=\"/asset/css/prism.css\"/><script src=\"/asset/js/prism.js\"></script>")

Editing the backend configuration functions

This overrides the default back-end filter for the src-block and changes the HTML template. This will also change the class to match Prism’s recommendations, which is language-*, where * is the language.

This code is originally from here

(defun my/org-html-src-block (src-block _contents info)
  "Transcode a SRC-BLOCK element from Org to HTML.
  CONTENTS holds the contents of the item.  INFO is a plist holding
  contextual information."
  (if (org-export-read-attribute :attr_html src-block :textarea)
      (org-html--textarea-block src-block)
    (let* ((lang (org-element-property :language src-block))
           (code (org-html-format-code src-block info))
           (label (let ((lbl (org-html--reference src-block info t)))
                    (if lbl (format " id=\"%s\"" lbl) "")))
           (klipsify  (and  (plist-get info :html-klipsify-src)
                            (member lang '("javascript" "js"
                                           "python" "scheme" "clojure" "php" "html" "shell" "rust")))))
      (if (not lang) (format "<pre class=\"example\"%s>\n%s</pre>" label code)
        (format "<div class=\"org-src-container\">\n%s%s\n</div>"
                ;; Build caption.
                (let ((caption (org-export-get-caption src-block)))
                  (if (not caption) ""
                    (let ((listing-number
                           (format
                            "<span class=\"listing-number\">%s </span>"
                            (format
                             (org-html--translate "Listing %d:" info)
                             (org-export-get-ordinal
                              src-block info nil #'org-html--has-caption-p)))))
                      (format "<label class=\"org-src-name\">%s%s</label>"
                              listing-number
                              (org-trim (org-export-data caption info))))))
                ;; Contents.
                ;; Changed HTML template to work with Prism.
                (if klipsify
                    (format "<pre><code class=\"src language-%s\"%s%s>%s</code></pre>"
                            lang
                            label
                            (if (string= lang "html")
                                " data-editor-type=\"html\""
                              "")
                            code)
                  (format "<pre><code class=\"src language-%s\"%s>%s</code></pre>"
                          lang label code)))))))

Next, a new backend that includes the new filter needs to be defined. According to org-mode’s documentation, we can do that by calling org-export-define-derived-backend, specifying the derived backend’s name and the derived backend’s parent as the first and second parameter, and modifying the :translate-alist property to include the new filter.

(org-export-define-derived-backend 'site-html
                                   'html
                                   :translate-alist
                                   '((src-block . my/org-html-src-block)))

Customise the HTML output by using our own scripts and styles without showing our validations links

(setq org-html-validation-link nil org-html-head-include-scripts nil
      org-html-head-include-default-style nil
      org-html-head set-css)

This is a wrapper function editing the original org-publish-to-html function

(defun my/org-publish (plist filename pub-dir)
  "Publish the file only if it contains the line '#+SELECT_TAGS: publish'."
        (org-publish-org-to 'site-html filename
                            (concat (when (> (length org-html-extension) 0) ".")
                                    (or (plist-get plist :html-extension)
                                        org-html-extension
                                        "html"))
                            plist pub-dir))

Setting up org-publish

(setq org-publish-project-alist
      (list

       (list "posts"
             :recursive nil
             :base-directory "./posts"
             :publishing-function 'my/org-publish
             :publishing-directory "./public/posts"
             :with-email nil
             :auto-sitemap t
             :sitemap-title "posts"
             :sitemap-filename "posts.org"
             :with-creator t
             :with-author nil
             :with-toc t
             :section-numbers nil
             :time-stamp-file nil)
       (list "pages"
             :recursive nil
             :exclude "README.org"
             :base-directory "./"
             :publishing-function 'my/org-publish
             :publishing-directory "./public/"
             :with-email nil
             :with-creator t
             :with-author nil
             :with-toc t
             :section-numbers nil
             :time-stamp-file nil)

       (list "static"
         :base-directory "./"
         :base-extension "css\\|txt\\|jpg\\|gif\\|png\\|js"
         :recursive t
         :publishing-directory  "./public"
         :publishing-function 'org-publish-attachment)

       (list "site" :components (list "pages" "static" "posts"))
))

Generate the site output

(org-publish-all t)

TODOs

  • [ ] fix mobile view
  • [ ] move table of contents to the right side
  • [ ] add contents of post/preamble to the build post
  • [ ] add hot reload

Emacs 29.2 (Org mode 9.6.15)