UP | HOME

publish.el

publish

we can (org-publish-all t) here to force it to rebuild everything

and the repository’s readme becomes the front page for the website

bash :comments both :mkdirp yes :tangle publish :shebang #!/bin/bash

git add .
git commit -m sync
git pull
git push
emacs -nw --load .publish.el --eval "(progn (org-publish-all $1) (kill-emacs))"
cp public/readme.html public/index.html
rsync -rv public/ party:/blog/notebook/

.publish.el

TODO
use #!/usr/bin/env -S emacs -x when emacs 29 is stable

the original version of this file was gitlab’s org mode pages example.

it’s been modified quite a bit though.

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

;; publish.el --- Publish org-mode project on Gitlab Pages
;; Author: Rasmus, chee

;;; Commentary:
;; This script will convert the org-mode files in this directory into
;; html.

;;; Code:

ox-publish has to be loaded

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(use-package ox-publish :straight nil)
(use-package time-stamp :straight nil)

i’m prioritising passing builds over broken pages, because i’d rather have the latest content live even if there are broken links.

if i link with to a file that only exists on my personal computer, i don’t want that to break the build.

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(setq
       org-export-with-broken-links t
       org-export-with-title t

i don’t need section numbers, i don’t need table of contents (but maybe i’ll bring it back?)

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

org-export-with-section-numbers nil
org-export-with-toc nil

curlies are cute!

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

org-export-with-smart-quotes t

um

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

org-export-use-babel t
org-confirm-babel-evaluate nil)

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(setq org-babel-load-languages
                      '((emacs-lisp . t)
                              (makefile . t)
                              (dot . t)
                              (css . t)
                              (sass . t)
                              (js . t)
                              (shell . t)
                              (ruby . t)
                              (zig . t)))

It’s cute to have my own theme used for src blocks

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(setq org-src-fontify-natively t)
(load-theme 'lychee t)

create slightly nicer html. it’s still pretty FrontPagey though

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(setq org-html-divs
       '((preamble "header" "top")
               (content "main" "content")
               (postamble "footer" "postamble"))
       ;; org-html-klipsify-src t
       org-html-container-element "section"
       org-html-metadata-timestamp-format "%Y-%m-%d"

the html checkboxes aren’t helpful, because you can toggle them but they don’t do anything. they are anti-useful.

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

org-html-checkbox-type 'unicode
org-html-html5-fancy t
org-html-validation-link nil
org-html-doctype "html5")

file extensions in this list get copied over to public/ during publish

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(defvar site-attachments
       (regexp-opt
              '(
                      "jpg" "jpeg" "gif" "png" "svg"
                      "webm" "mp4" "m4a" "mp3" "flac"
                      "aif" "ico" "cur" "css" "js" "woff"
                      "html" "pdf" "el"))
       "File types that are published as static files.")

prepublish

before publishing, we give all the headings a CUSTOM_ID of a readable slug, for nicer anchor links. if there are clashes, the browser will go to the first one. we are OK with that 🆗🆗😎👍🏼🆒🆒🆒 🆓

heading slugs

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(defun number-within-range? (number range)
       (and (>= number (car range)) (<= number (cdr range))))

(defvar emoji-ranges
       '((9984 . 10175)
              (126976 . 127023)
              (127136 . 127231)
              (127248 . 127386)
              (127462 . 129479)))

(defun emoji? (char)
       (seq-some
        (lambda (range)
               (number-within-range? char range))
        emoji-ranges))

(defun emoji-name-or-identity (char)
       "Return emoji name if CHAR is emoji, otherwise return CHAR."
       (if (emoji? char)
                (get-char-code-property char 'name)
              (char-to-string char)))

(defun replace-emojis-with-names-in-string  (string)
       "Return a string that is STRING with emojis replaced by their names."
       (mapconcat 'emoji-name-or-identity (string-to-list string) ""))

(defun slugify (headline)
       "Create a slug from a HEADLINE string."
       (replace-regexp-in-string
        "-+" "-"
        (replace-regexp-in-string
              "-:[:a-z0-9]+:$" ""
              (replace-regexp-in-string
               "^\\(todo\\|done\\)?-" ""
               (replace-regexp-in-string
                "[^a-z0-9+=~:@]" "-"
                (downcase
                      (replace-emojis-with-names-in-string headline)))))))

(defun /notebook-add-heading-slugs (_)
       "Add slugified heading CUSTOM_IDs to entries.
Will be used for the heading id in the html file."
       (org-map-entries
        (lambda nil
               (org-set-property "CUSTOM_ID" (slugify (downcase (org-get-heading)))))))

(add-hook 'org-export-before-parsing-hook '/notebook-add-heading-slugs)

exposed header args

we expose the header args so it’s more clear to the reader what details of the file is being tangled:

  • file name
  • file permissions
  • shebang

these are styled here: .src-header-args

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(defun /notebook-expose-header-args (_)
              (end-of-buffer)
              (while (re-search-backward org-babel-src-block-regexp nil t)
                (let
                       ((header-args
                               (buffer-substring
                                      (+ 11 (point))
                                      (save-excursion (end-of-line) (point)))))
                       (beginning-of-line)
                       (newline)
                       (forward-line -1)
                       (insert
                              (format "#+begin_src-header-args\n  %s\n#+end_src-header-args\n" header-args)))))

       (add-hook 'org-export-before-processing-hook '/notebook-expose-header-args)

publish

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

(setq org-publish-project-alist
              (list
                (list "notebook"
                       :base-directory "."
                       :base-extension "org"
                       :recursive t
                       :publishing-function 'org-html-publish-to-html
                       :publishing-directory "./public"
                       :exclude (regexp-opt '("draft"))
                       :auto-sitemap nil
                       :html-link-home "https://chee.party/notebook/"
                       :html-link-up "https://chee.party/notebook/"

i pull in my favicon, my style and script.

there isn’t actually a script file yet, but the style file is documented here style.html

emacs-lisp :comments both :mkdirp yes :tangle .publish.el

                :html-head-extra (format "<link rel=\")icon\" type=\"image/x-icon\" href=\"/.attachments/favicon.ico\"/><link rel=\"stylesheet\" href=\"/notebook/.attachments/style.css?%s\"><script async defer src=\"/notebook/.attachments/script.js?%s\"></script>" (random) (random))
                :sitemap-title "chee's notebook"
                :with-title t
                :use-babel t
                :html-self-link-headlines t
                :sitemap-sort-files 'alphabetically)
              (list "attachments"
                :base-directory "."
                :exclude "public/"
                :base-extension site-attachments
                :publishing-directory "./public"
                :publishing-function 'org-publish-attachment
                :recursive t)
              (list "notes" :components '("notebook"))))

(provide 'publish)
;;; publish.el ends here