Blogging with Org publishing

Published on Sep 08, 2018

This post talks about my blogging setup. I am using simple plain org-publishing for blogging here. There is a link to the complete source in the footer. I assume that the reader has already skimmed through documentation available at Org Publishing.

We will go through the setup in steps. Here is the layout of files that make up the blog:

├── archive.html
├── archive.xml
├── assets
│   ├── css
│   │   └── bootstrap.css
│   ├── images
│   │   └── emacs.svg
│   └── js
│       ├── custom.js
│       └── turbolinks.js
├── index.html
├── posts
│   ├── lcm-hcf-as-ops-on-sets.html
│   └── ...
└── src
    ├── archive.org
    ├── assets/
    ├── drafts/
    ├── index.org
    ├── org-blog.el
    └── posts
        ├── blogging-with-org.org
        ...

New blog posts are added as org files inside src/posts/. These files are then exported to HTML through org-publishing and kept in posts/. The directory tree structure under src/ is almost replicated under the top-level directory.

The org project definition is in src/org-blog.el. We load src/org-blog.el. From one of the project org-files, we can initiate an export for the project with C-c C-e. Then choose, P for publishing and select the current project.

Org-publish gives us hooks that are called during various phases of exporting org files to HTML. These hooks are simple functions that take metadata about the project as input (mainly as a property list) and return HTML for pieces of a page or the whole page.

We will go through the project definition in src/org-blog.el now.

A few imports:

(require 'org)
(require 'ox-publish)
(require 'ox-html)
(require 'org-element)
(require 'ox-rss)

This function simply touches index.org so that it is always exported anew even if none of the other files in the project changed. org-blog-prepare is called before any of the other functions in org-publish.

(defun org-blog-prepare (project-plist)
  "With help from `https://github.com/howardabrams/dot-files'.
  Touch `index.org' to rebuilt it.
  Argument `PROJECT-PLIST' contains information about the current project."
  (let* ((base-directory (plist-get project-plist :base-directory))
         (buffer (find-file-noselect (expand-file-name "index.org" base-directory) t)))
    (with-current-buffer buffer
      (set-buffer-modified-p t)
      (save-buffer 0))
    (kill-buffer buffer)))

This function adds a few sytlesheets to the <head> of exported HTML files. Anything that is required to be placed into the <head> should go here.

(defvar org-blog-head
  "<link rel=\"stylesheet\" type=\"text/css\" href=\"/assets/css/bootstrap.css\"/>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://fonts.googleapis.com/css?family=Amaranth|Handlee|Libre+Baskerville|Bree+Serif|Ubuntu+Mono|Pacifico&subset=latin,greek\"/>
  <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"favicon.ico\">")

org-publish organizes content of a page into preamble, post contents and post-amble. Each of these can be provided to org-publish through functions that take a property list as an argument and return an HTML string.

For this blog, the preamble is a simple banner and two href links.

(defun org-blog-preamble (_plist)
  "Pre-amble for whole blog."
  "<div class=\"banner\">
    <a href=\"/\"> Ramblings from a Corner </a>
  </div>
  <ul class=\"banner-links\">
    <li><a href=\"/\"> About Me </a> </li>
    <li><a href=\"/archive.html\"> Posts </a> </li>
  </ul>
  <hr>")

The post-amble for this blog has a nice Emacs icon, Google Analytics and Javascript for Disqus threads. It also shows an example of using the plist that gets passed into this function by org-publish. To find out all the keys in input plist, you can print it once during the publishing process. Here, we use the fact that all ours posts are kept under posts/ path to decide whether we should show a Disqus comment block or not. We get the path of the input file from :input-file key in input plist.

(defun org-blog-postamble (plist)
  "Post-amble for whole blog."
  (concat
  "<footer class=\"footer\">
      <!-- Footer Definition -->
   </footer>

  <!-- Google Analytics Js -->"

   ;; Add Disqus if it's a post
   (when (s-contains-p "/posts/" (plist-get plist :input-file))
   "<!-- Disqua JS --> ")))

These two functions are for printing the archive of blog posts. Org-publish already has this feature. We are only configuring how post links and the dates of their publishing should be displayed. org-blog-sitemap-format-entry formats a single blog entry and returns its HTML. Here, it's returning a simple <span> element with the date and the title of the blog post. org-blog-sitemap-function is then passed formatted entries derived using org-blog-sitemap-format-entry as a list argument. It then returns the HTML for the whole index/archive.

(defun org-blog-sitemap-format-entry (entry _style project)
  "Return string for each ENTRY in PROJECT."
  (when (s-starts-with-p "posts/" entry)
    (format "@@html:<span class=\"archive-item\"><span class=\"archive-date\">@@ %s @@html:</span>@@ [[file:%s][%s]] @@html:</span>@@"
            (format-time-string "%h %d, %Y"
                                (org-publish-find-date entry project))
            entry
            (org-publish-find-title entry project))))

(defun org-blog-sitemap-function (title list)
  "Return sitemap using TITLE and LIST returned by `org-blog-sitemap-format-entry'."
  (concat "#+TITLE: " title "\n\n"
          "\n#+begin_archive\n"
          (mapconcat (lambda (li)
                       (format "@@html:<li>@@ %s @@html:</li>@@" (car li)))
                     (seq-filter #'car (cdr list))
                     "\n")
          "\n#+end_archive\n"))

This last function is a hack that I use to wrap contents of published org-mode files into Bootstrap's row and column =<div>=s. This function is exactly same as the original function but opens the file and rewrites the HTML. This causes the value of (point) to change fanatically for me while Emacs is exporting the project and is working on the file visited by my current buffer. I have grown accustomed to see this as progress indication for the export process. :)

(defun org-blog-publish-to-html (plist filename pub-dir)
  "Same as `org-html-publish-to-html' but modifies html before finishing."
  (let ((file-path (org-html-publish-to-html plist filename pub-dir)))
    (with-current-buffer (find-file-noselect file-path)
      (goto-char (point-min))
      (search-forward "<body>")
      (insert (concat "\n<div class=\"content-wrapper container\">\n "
                      "  <div class=\"row\"> <div class=\"col\"> </div> "
                      "  <div class=\"col-sm-6 col-md-8\"> "))
      (goto-char (point-max))
      (search-backward "</body>")
      (insert "\n</div>\n<div class=\"col\"></div></div>\n</div>\n")
      (save-buffer)
      (kill-buffer))
    file-path))

With all of the above function definitions, we are now ready to glue everything together by setting the list of projects known to org-publish. Information about the keys used here can be found in different sections of documentation for Org Publishing. If you have more than one projects, you might want to add-to-list instead of setting org-publish-project-alist.

(setq org-publish-project-alist
      `(("orgfiles"
         :base-directory "~/blog/src/"
         :exclude ".*drafts/.*"
         :base-extension "org"

         :publishing-directory "~/blog/"

         :recursive t
         :preparation-function org-blog-prepare
         :publishing-function org-blog-publish-to-html

         :with-toc nil
         :with-title t
         :with-date t
         :section-numbers nil
         :html-doctype "html5"
         :html-html5-fancy t
         :html-head-include-default-style nil
         :html-head-include-scripts nil
         :htmlized-source t
         :html-head-extra ,org-blog-head
         :html-preamble org-blog-preamble
         :html-postamble org-blog-postamble

         :auto-sitemap t
         :sitemap-filename "archive.org"
         :sitemap-title "Blog Posts"
         :sitemap-style list
         :sitemap-sort-files anti-chronologically
         :sitemap-format-entry org-blog-sitemap-format-entry
         :sitemap-function org-blog-sitemap-function)

        ("assets"
         :base-directory "~/blog/src/assets/"
         :base-extension ".*"
         :publishing-directory "~/blog/assets/"
         :publishing-function org-publish-attachment
         :recursive t)

        ("rss"
         :base-directory "~/blog/src/"
         :base-extension "org"
         :html-link-home "https://vicarie.in/"
         :html-link-use-abs-url t
         :rss-extension "xml"
         :publishing-directory "~/blog/"
         :publishing-function (org-rss-publish-to-rss)
         :exclude ".*"
         :include ("archive.org")
         :section-numbers nil
         :table-of-contents nil)

        ("blog" :components ("orgfiles" "assets" "rss"))))

With this setup, I can now publish posts with C-c C-e P p from any of the org files in this project. I like to have a look at the post locally before publishing. To do so, I have a prodigy service defined:

(prodigy-define-service
  :name "[email protected]"
  :command "python2"
  :args '("-m" "SimpleHTTPServer" "8000")
  :cwd "~/blog/"
  :tags '(file-server)
  :stop-signal 'sigkill
  :kill-process-buffer-on-stop t)

Share your thoughts on the setup in the comments below. Thanks for reading.