Building a website with Emacs
I would like to make a confession… I detest most websites I visit on a daily basis. I wanted to make a difference with my little personal corner of the internet. What do you know? Emacs can totally do that.
You see, Emacs packs an exceptional mode, called Org Mode, that comes
with many wonderful functionalities, that which powers this website
goes by org-publish-project-alist
.
In short, you specify the location of a particular thing, and where it should go upon export, as a result, we have the utmost freedom to design our own file hierarchy - here's what I've come up with:
~/projects/grtcdr.github.io ├── org/ │ ├── blog/ │ ├── css/ │ ├── data/ │ ├── img/ │ ├── index.org │ └── contact.org │ └── html/ ------ This directory is generated by org-publish ├── blog/ ├── css/ ├── data/ ├── img/ ├── index.html └── contact.html
1. An example to get you started
I'd like to introduce you to the most basic form of a working org-publish-project-alist
.
(require 'ox-html) (setq org-publish-project-alist '(("example.com/org" :base-directory "~/website/org/" :base-extension "org" :publishing-directory "~/website/html/" :publishing-function org-html-publish-to-html)))
What happens when you create a .org
document within your
:base-directory
– save it – hit M-x org-publish RET
, is nothing
short of magic!
Org, being the smarty-pants tool it is, takes the original file and
publishes it to :publishing-directory
in HTML
format.
That's exactly what we would need if we had tens or hundreds of .org
documents and wanted to automate the export process.
Things to keep in mind:
:base-extension
expects an extension, without a.
prefix.:publishing-function
depends on the type of content you intend to export.
Let's have a look at another example, though this time, we'll export some stylesheets, too.
(setq org-publish-project-alist '(("example.com/org" :base-directory "~/website/org/" :base-extension "org" :publishing-directory "~/website/html/" :publishing-function org-html-publish-to-html) ("example.com/css" :base-directory "~/website/css/" :base-extension "css" :publishing-directory "~/website/css/" :publishing-function org-publish-attachment)))
Now we have two different publishing projects, one for org
documents
and another for css
.
The main differences being, the :publishing-function
and the project
identifier, i.e. the CAR
of the association list. My advice, pick
meaningful names for your project identifiers. It doesn't necessarily
have to start with a URL, that's just my preference.
Okay! This is enough to get your website rolling, although you might want to see the next section for a more complicated setup.
2. Initialization
I always like to experiment with the structure of my $HOME
, which
leaves me with a temporarily broken setup sometimes.
I took the steps to remedy this with a few functions, which I'll use
with the :base-directory
and :publishing-directory
properties, the
more sensitive area of this setup.
(defvar site-base-directory "~/projects/grtcdr.github.io/" "The base of the site where files will are to be processed and exported.") (defun site-content-directory (context &optional directory) "Prefixes the provided DIRECTORY with the ’site-base-directory’ given the CONTEXT." (cond ((equal context 'publish) (concat site-base-directory "html/" directory)) ((equal context 'base) (concat site-base-directory "org/" directory))))
site-base-directory
is the base location of my site, and
site-content-directory
will build a path depending on the parameters
I pass. This makes it a lot safer if I ever decide to move things
around.
3. Setup
Now I'd like to present my setup. It does all of the heavy lifting for me, and allows me to focus on more important things such as chatting with strangers on IRC.
(use-package ox-publish :custom (org-publish-project-alist `(("grtcdr.github.io/content" :base-directory ,(site-content-directory 'base) :base-extension "org" :publishing-directory ,(site-content-directory 'publish) :publishing-function org-html-publish-to-html :exclude "\\(README\\|setup\\).org" :recursive t :sitemap-filename "index.org" :sitemap-title "grtcdr's website" :with-author nil :with-creator nil :with-date nil :with-email nil :with-title nil :with-toc nil :section-numbers nil) ("grtcdr.github.io/img" :base-directory ,(site-content-directory 'base "img/") :base-extension "png\\|jpe?g" :publishing-directory ,(site-content-directory 'publish "img/") :publishing-function org-publish-attachment) ("grtcdr.github.io/blog" :base-directory ,(site-content-directory 'base "blog/") :base-extension "org" :publishing-directory ,(site-content-directory 'publish "blog/") :publishing-function org-html-publish-to-html :exclude ".\\(setup\\|navigation\\).org" :with-date t :with-title nil :with-toc nil :section-numbers nil) ("grtcdr.github.io/css" :base-directory ,(site-content-directory 'base "css/") :base-extension "css" :publishing-directory ,(site-content-directory 'publish "css/") :publishing-function org-publish-attachment) ("grtcdr.github.io/data" :base-directory ,(site-content-directory 'base "data/") :base-extension "txt\\|pdf" :publishing-directory ,(site-content-directory 'publish "data/") :publishing-function org-publish-attachment) ("grtcdr.github.io" :components ("grtcdr.github.io/img" "grtcdr.github.io/data" "grtcdr.github.io/css" "grtcdr.github.io/content" "grtcdr.github.io/blog")))))
There's a project identifier for every component of the website, and the last one groups them all together, so that I don't have to selectively export every single one.
If you'd like to explore this topic in greater detail, please read the wonderful documentation provided by the Org project.