publish.el

.snootclub/ci.yml

snootforge has a gitlab-runner that runs this file.

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

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

yaml :comments both :mkdirp yes :tangle .snootclub/ci.yml

pages:
  stage: deploy
  script:
    - emacs --batch --no-init-file --load .publish.el --eval '(org-publish-all t)'
    - cp public/readme.html public/index.html
  artifacts:
    paths:
      - public
  only:
    - main

.publish.el

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

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

;;; Code:

bootstrap straight.el

we pull down straight.el and use-package because they are a nice way to work with dependencies

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

(defvar bootstrap-version)
(let ((bootstrap-file
        (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
       (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
      (url-retrieve-synchronously
        "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
        'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))
(setq package-enable-at-startup nil)
(setq straight-check-for-modifications '(watch-files find-when-checking))
(setq straight-use-package-by-default t)
(straight-use-package 'use-package)

dependencies

we’re gonna need org-mode

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

(use-package org
  :straight org-plus-contrib)
(use-package htmlize)

and a few packages to make sure things are highlighted nicely

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

(use-package rustic)
(use-package json-mode)
(use-package gitignore-mode)
(use-package gitconfig-mode)
(use-package rainbow-mode)
(use-package editorconfig)
(use-package time-stamp :straight nil)
(use-package s)
(use-package dash)
(use-package fish-mode)

rustic depends on lsp-mode, i think i can turn that off with a variable but i haven’t checked.

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

(use-package lsp-mode)

publish settings

ox-publish has to be loaded

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

(use-package ox-publish :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)
     (scheme . 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)
(add-to-list
  'custom-theme-load-path ".themes/")
(load-theme 'pale-rose 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

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>" (time-stamp-string "%#A-%02d-%#B-%02I:%02M:%02S.%#p")(time-stamp-string "%#A-%02d-%#B-%02I:%02M:%02S.%#p"))
      :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