org-mode for Static Website GenerationTags: meta website emacs
This post no longer has a proper context; I am no longer using the following approach to generate my website. That said, I think it still contains some content that might be useful for someone else trying to use org-mode for static website generation.
I was unhappy with my previous website because the overhead of presenting content was high. It is my opinion that presenting content on a website should require as little non-essential work as possible; generating content (e.g., writing) is hard enough.
The Previous Site
The previous site used pagegen. Pagegen is a static web page site generator that relies on a site template file and a directory structure. Content for the site is stored in text markup files. The template can contain variables referring to components of the markup file. Explained at this level of detail, pagegen should be sufficient for my needs.
Realizing pagegen was not the right system for my needs came from using the markup language. Specifically, the textile variant that pagegen uses is something I have never seen before or since. Therefore, becoming familiar with the textile markup language to present content on my website would not be something I would likely find useful elsewhere.
To avoid sinking time into a technology with limited context, I generate my content in HTML then escaped all of it in textile. This increased the overhead of generating and presenting knowledge; creating simply structured content became a mess of XML. Naturally, I just avoided it.
Onward to org-mode
What is essential when presenting content for my site? The answer I arrived at is some simple way to structure content and some way to export this structure to HTML.
Org-mode has a configurable export system that will export org documents to other formats. There is some process for implementing conversions to target formats however, exporting to LaTeX and HTML has already been implemented.
After using emacs org-mode for a couple months, I realized that most of the structure I want when presenting content can be given using the most basic org-mode markup. Structures like sections, tables, lists, and typical text styles can all be described intuitively. Additionally, time spent understanding the markup and system in-depth would not be wasted; I have already committed to integrating org-mode into my daily habits.
Assuming emacs and the org-mode module are installed, to get org-to-HTML functionality to work add the appropriate configuration. I will develop this org-to-HTML export configuration in a peicewise fashion. This configuration should reside in a place such that emacs will execute it upon configuration.
First we must require org-mode’s publishing functionality.
Editing my site content is a three phase process consisting of
writing, editing, and publishing. During the writing and editing
phases, I want to publish to my harddrive and review my work via a
local path like
file:///path/to/local/deployment/site/ in a
browser. When I am satisfied with the content I publish it. I also
want to structure content into different directories and refer to the
content by relative paths. For example, I wanted all files to refer to
a stylesheet in the
css/ directory. I accomplished this by setting
base element in the head of all generated HTML files according
to whether I want to view it locally or if I wanted to publish. How
these variables are used to construct the head will follow shortly.
Unfortunately, the solution below for toggling between the firt two
phases and the last requires I change
base-ref and execute my
(load-file "file") (If you have a suggestion on
how to fix this please leave a comment!).
(setf local-base-ref "file:///home/dacosta/gitrepositories/documents/org/web/chaosape.com/build/") (setf chaosape-base-ref "http://www.chaosape.com/") (setq base-ref local-base-ref)
From what I have gather, org-mode HTML exporting has two builtin
org-publish-attachment. The former converts org files to HTML then
places the HTML in some destination directory and the latter copies
the file to the destination directory. I have three pairs of variables
defining content source and destination for org, css, and image
(setf chaosape-repo-location "/home/dacosta/gitrepositories/documents/org/web/chaosape.com/") (setf chaosape-css-location "css/") (setf chaosape-img-location "img/") (setf chaosape-src-location (format "%s%s" chaosape-repo-location "src/")) (setf chaosape-src-css-location (concat chaosape-src-location chaosape-css-location)) (setf chaosape-src-img-location (concat chaosape-src-location chaosape-img-location)) (setf chaosape-dst-location (format "%s%s" chaosape-repo-location "build/")) (setf chaosape-dst-css-location (format "%s%s" chaosape-dst-location chaosape-css-location)) (setf chaosape-dst-img-location (concat chaosape-dst-location chaosape-img-location ))
Org-modes HTML publishing functionality provides a configuration
option to set HTML header files uniformly. Here I define a variable
containing the content I would like place in the head of all generated
HTML files. The
base href must be set in the head so here we build
the final head content using a format string and
I also wanted a footer.
(setf chaosape-footer " <a style=\"border-bottom: none;\" href=\"http://www.twitter.com/chaosape/\"> <img src=\"img/twitter.ico\" alt=\"chaosape on Twitter\"/> </a> <a style=\"border-bottom: none;\" href=\"http://www.bitbucket.com/chaosape/\"> <img src=\"img/bitbucket.png\" alt=\"chaosape on bitbucket\"> </a> <a style=\"border-bottom: none;\" href=\"http://www.github.com/chaosape/\"> <img src=\"img/github.ico\" alt=\"chaosape on github\"> </a> <a style=\"border-bottom: none;\" href=\"http://about.me/chaosape/\"> <img style=\"height: 32px; width:32\" src=\"img/aboutme.ico\" alt=\"chaosape on about me\"> </a> ")
There are three components associated with the chaosape.com
project. The component chaosape.com-top recursively converts all org
files in the source directory
chaosape-src-location and places them
in a corresponding directory with a root at
chaosape-dst-location. The components chaosape.com-css and
chaosape.com-img copy content from the directories
respectively. Once this configuration is in place calling
org-publish-all should generate content rooted at
chaosape-dst-location. You may find additional information about
this configuration here.
(setf org-publish-project-alist `(("chaosape.com" :components ("chaosape.com-top" "chaosape.com-css" "chaosape.com-img")) ("chaosape.com-top" :base-directory ,chaosape-src-location :publishing-directory ,chaosape-dst-location :base-extension "org" :recursive t :html-head ,chaosape-header :publishing-function org-html-publish-to-html :with-author t :with-creator nil :with-latex t :export-with-tags t :export-creator-info nil :export-author-info nil :auto-postamble nil :table-of-contents nil :section-numbers nil :style-include-default nil :section-numbers nil :with-toc nil :html-postamble ,chaosape-footer :html-head-include-default-style nil :html-head-include-scripts nil ) ("chaosape.com-css" :base-directory ,chaosape-src-css-location :publishing-directory ,chaosape-dst-css-location :base-extension "css" :publishing-function org-publish-attachment ) ("chaosape.com-img" :base-directory ,chaosape-src-img-location :publishing-directory ,chaosape-dst-img-location :base-extension "jpg\\|gif\\|png\\|svg" :publishing-function org-publish-attachment ) ) )
I wanted my index page to contain a listing of all of my blogs ordered
by published date. The following function generates such a list based
on org files in a directory named
blog (Much of this code was lifted
then modified). We should be able to inline the output of this
index.org. I was unable to get this to work
correctly. However, I was able to get the dynamic block generation
(defun org-dblock-write:generate-bloglist (params) (let* ((dir "blog") (files (directory-files dir t "^[^\\._][^#].*\\.org$" t)) entries) (dolist (file files) (catch 'stop (let* ((path (concat dir "/" (file-name-nondirectory file))) (env (org-combine-plists (org-babel-with-temp-filebuffer file (org-export-get-environment)))) (date (or (apply 'encode-time (org-parse-time-string (or (car (plist-get env :date)) (throw 'stop nil)))))) (last-modified-date (nth 5 (file-attributes file)))) (plist-put env :file (file-name-nondirectory file)) (plist-put env :path path) (plist-put env :parsed-date date) (plist-put env :last-modified-date last-modified-date) (push env entries)))) (dolist (entry (sort entries (lambda (a b) (time-less-p (plist-get b :parsed-date) (plist-get a :parsed-date))))) (insert (format "* [[file:blog/%s][%s]]\n- %s\n- Last Modified on %s\n- Created on %s\n" (plist-get entry :file) (car (plist-get entry :title)) (plist-get entry :description) (format-time-string "%B %e %Y" (plist-get entry :last-modified-date)) (format-time-string "%B %e %Y" (plist-get entry :parsed-date))))))) (add-hook 'org-export-before-processing-hook (lambda (backend) (org-update-all-dblocks)))
I found that the above code left some spurious text on the page. I am not exactly sure why and I am not familiar enough with org-mode to know how to remove it; I removed this text by writing a filter that modified the final output org-mode was to publish.
(defun org-html-remove-spurious-dynamic-block-code (string backend info) "Remove #+BEGIN: generate-bloglist." (when (and (org-export-derived-backend-p backend 'html) (string-match ".\+BEGIN: generate-bloglist" string)) (replace-regexp-in-string ".\+BEGIN: generate-bloglist" "" string))) (add-to-list 'org-export-filter-final-output-functions 'org-html-remove-spurious-dynamic-block-code)
I need the navigation menu to be placed uniformly in the content div element (all exported org structured text ends up within this div). I was able to get the menu placed correctly by writing another filter.
(defun org-html-add-navigation-after-content-div (string backend info) "Put the navigation menu inside the content div." (when (and (org-export-derived-backend-p backend 'html) (string-match "div id=\"content\"" string)) (replace-regexp-in-string "<div id=\"content\">" (format "<input type=\"checkbox\" id=\"nav-trigger\" class=\"nav-trigger\" /> <label for=\"nav-trigger\"></label> <ul class=\"nav\"> <li class=\"nav-element\"><a href=\"index.html\">home</a></li> <li class=\"nav-element\"><a href=\"cv.html\">cv</a></li> </ul> <div class=\"content\">" )string))) (add-to-list 'org-export-filter-final-output-functions 'org-html-add-navigation-after-content-div)
I think this site setup will facilitate more frequent content creation. This is something I need to make a habit of if I intend to improve my writing skills. One gripe I have about this setup is its dependency on this dynamic block magic that requires me to write a filter to remove some spurious text.