shrirambo.github.io/home/blog

Website Publishing: Build config

Table of Contents

Emacs Build Configuration

Save package in local .packages folder

This will set a local package initialisation directory so that packages are downloaded and saved locally and not in users or systems .emacs.d folder

(require 'package)
(setq package-user-dir (expand-file-name "./.packages"))

Package archive path

This add package archives path, the location where package manager will search for the required packages.

(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("elpa" . "https://elpa.gnu.org/packages/")))

Initialize package system

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

Adding usepackage package

For nicer imports of packages

(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)

Raimbow delimiters

(use-package rainbow-delimiters
  :ensure t
  :hook ((prog-mode . rainbow-delimiters-mode)
         (org-mode . rainbow-delimiters-mode)))

No backups or temp files

(setq make-backup-files nil)

Install dependencies

; (use-package ox-slimhtml
 ;  :ensure t)
 (package-install 'htmlize)

Load publishing system

(require 'ox-publish)

Evaluate lisp code without confirmation

(defun my-org-confirm-babel-evaluate (lang body)
  (not (member lang '("emacs-lisp" ))))
(setq org-confirm-babel-evaluate 'my-org-confirm-babel-evaluate)

Custom Backend

Markup alist

(assq-delete-all 'verbatim org-html-text-markup-alist)
(add-to-list 'org-html-text-markup-alist
             '(verbatim . "<mark>%s</mark>"))

Sitemap or Index page

A function to format the publication date as a string which will be shown below the entry on index page.

(defun sr/format-date-subtitle (file project)
  "Format publication date. Use (org-publish-find-property file :title project) to add other properties"
  (capitalize (format-time-string "%B %d, %Y" (org-publish-find-date file project))))   

A function to get the description of the entry.

(defun sr/format-description (file project)
  "Format publication date. Use (org-publish-find-property file :title project) to add other properties
This works with some specific properties and not all custom property so the following hack."
   (concat "" (with-temp-buffer 
     (insert-file-contents (concat "./content/" file) )
     (cdr
      (assoc "DESCRIPTION" 
             (org-element-map
                 (org-element-parse-buffer 'element) 'keyword
               (lambda (keyword) (cons (org-element-property :key keyword)
                                  (org-element-property :value keyword)))))))))

Thanks to The Kitchin Research Group Blog for the solution.

TODO: Replace ./content in above with something like (plist-get project :base-directory) but that works.

Now I need a function that will format the sitemap/index page entries in a specified way.

(defun sr/org-publish-sitemap-entry (entry style project)
  "Format for sitemap ENTRY, as a string.
ENTRY is a file name.  STYLE is the style of the sitemap.
PROJECT is the current project."
  (unless (equal entry "404.org")
    (format "[[file:%s][%s]] \n %s \n /%s/"
            entry
            (org-publish-find-title entry project)
            (sr/format-description entry project)
            (sr/format-date-subtitle entry project)
            )))

And finally, a function to generate sitemap.org file.

(defun sr/org-publish-sitemap (title list)
  "Generate sitemap as a string, having TITLE.
LIST is an internal representation for the files to include, as
returned by `org-list-to-lisp'."
  (let ((filtered-list (cl-remove-if (lambda (x)
                                       (and (sequencep x) (null (car x))))
                                     list)))
    (concat "#+TITLE: " title "\n"
            "#+OPTIONS: tex:t toc:nil\n"
            "#+META_TYPE: website\n"
            "#+DESCRIPTION: Personal Blog of Shriram Ashirgade\n"
            "\n#+ATTR_HTML: :class sitemap\n" 
            (org-list-to-org filtered-list)
            )))

The above piece of code was borrowed from writepermission.com.

Derived backend

(org-export-define-derived-backend 'shrirambo-site-html 'html
  :translate-alist '((headline . my-latex-headline-translator)
                     (template . my-latex-template)))

*ambles

Function to retrieve template html

(defun sa/get-template (type)
  "Return the content for the pre/postamble of TYPE."
  `(("en" ,(with-temp-buffer
             (insert-file-contents (expand-file-name (format "%s.html" type) "resources/templates/"))
             (buffer-string)))))

Publish

Customize HTML output

;; Customize the HTML output
(setq org-html-validation-link nil            ;; Don't show validation link
      org-html-head-include-scripts nil       ;; Use our own scripts
      org-html-head-include-default-style nil ;; Use our own styles
      org-export-use-babel t                ;; Do(not) use babel to execute src blocks
      org-html-htmlize-output-type 'css     ;; Instead of using inline css for each element
      org-export-with-todo-keywords nil       ;; Ignore TODO keywords
      org-html-doctype "html5"
      org-html-html5-fancy nil
      org-html-head (concat
                      "<link rel='stylesheet' href='./css/code.css' />\n"
                     "<link rel='stylesheet' href='./css/simple.css' />\n"
                     "<link rel='shortcut icon' href='./img/favicon.ico' type='image/x-icon' />\n"
                     "<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/fork-awesome@1.2.0/css/fork-awesome.min.css' integrity='sha256-XoaMnoYC5TH6/+ihMEnospgm0J1PM/nioxbOUdnM8HY=' crossorigin='anonymous'>\n")
      org-html-self-link-headlines t
      org-html-container-element         "section"
      org-publish-timestamp-directory "./.timestamps/"
      org-html-divs '((preamble  "header" "top")
                       (content   "main"   "content")
                       (postamble "footer" "postamble"))

      )

Publishing project properties

;; Define the publishing project
(setq org-publish-project-alist
      (list
       (list "blog"
             :recursive t
             :base-directory "./content"
             :base-extension "org"
             :exclude ".*\.\\(setup\\|draft\\)\.org"  ;; This ignores all the setup and draft files
             :publishing-function 'org-html-publish-to-html
             :publishing-directory "./public"
             :with-author nil           ;; Don't include author name
             :with-creator nil            ;; Include Emacs and Org versions in footer
             :with-date nil
             :with-toc t                ;; Include a table of contents
             :section-numbers nil       ;; Don't include section numbers
             :time-stamp-file nil ;; Don't include time stamp in file
             :auto-sitemap t
             :sitemap-filename "index.org"
             :sitemap-style 'list
             :sitemap-title "Blog Posts"
             :sitemap-function  'sr/org-publish-sitemap  
             :sitemap-sort-files 'anti-chronologically
             :sitemap-format-entry 'sr/org-publish-sitemap-entry
             :html-preamble-format (sa/get-template 'preamble)
             :html-postamble t
             :html-postamble-format (sa/get-template 'postamble)
             )
       (list "microblog"
             :recursive t
             :base-directory "./microblog"
             :base-extension "org"
             :publishing-function 'org-html-publish-to-html
             :publishing-directory "./public"
             :with-author nil           ;; Don't include author name
             :with-creator nil            ;; Include Emacs and Org versions in footer
             :with-date nil
             :with-toc t                ;; Include a table of contents
             :section-numbers nil       ;; Don't include section numbers
             :time-stamp-file nil ;; Don't include time stamp in file
             :auto-sitemap nil
             ;:sitemap-filename "index.org"
             ;:sitemap-style 'list
             ;:sitemap-title "Blog Posts"
             ;:sitemap-function  'sr/org-publish-sitemap  
             ;:sitemap-sort-files 'anti-chronologically
             ;:sitemap-format-entry 'sr/org-publish-sitemap-entry
             :html-preamble-format (sa/get-template 'preamble)
             :html-postamble t
             :html-postamble-format (sa/get-template 'postamble)
             )
       (list "mail-site" :components '("blog" "microblog"))
      ))

Generate Output

;; Generate the site output
(org-publish-all t)
(message "Build complete!")

Build

First line evaluates thin org file and creates a build.el file which contains all the publishing configuration. The second line evaluates build.el file itself and publish the web pages. And the final line removes build.el file.

#!/bin/bash
emacs -Q --batch --eval "(require 'org)" --eval '(org-babel-tangle-file "build.org")'
emacs -Q --script build.el
rm build.el

One step less

Make emacs evaluate build.org itself, rahter than tangling build.el first. This will remove one step from the deployment.