How this site is made
How this site is made
We are at the end of 2017, and blogging software is still a major topic of discussion among geeks. Well, not really, let's say I still find it interesting. Hmm… Actually, no, it's not even that; I guess I just wanted to brag a bit about my setup. Is there anything worth bragging about in my setup? I'm not even sure. Still, please keep reading!
My objective with this site was to keep things simple: I wanted an easy and comfortable way to write the posts, minimal fuss when putting them online and as little server-side software as possible.
Writing posts
I naturally use emacs to write the posts for this website. I actually
use emacs for almost everything, as I said here before. I use org mode
for that, and each post is a separate .org
file. I only use a reduced
set of properties, where I indicate the date the post was written, the
author name, and the title. This means each post begins with the
following preamble:
#+TITLE: How this site is made #+AUTHOR: Nicolas Herry #+DATE: 2017/11/10 #+OPTIONS: toc:nil
By default, org mode generates a table of contents, and although
this might come in handy for some very long posts, it's generally
useless to me here. The property OPTIONS: toc:nil
disables this
behaviour.
Since I didn't want to type all this at the beginning of each and every file, I looked for a simple solution to insert it dynamically for me. There are many completion frameworks, and I could have written my own set of functions to do that, but there is already something called yasnippet which does an impressive job as a templating system. yasnippet is already part of my normal setup; all I had to do was create a template for my posts. This turned out to be very simple:
# -*- mode: snippet -*- # name: beastieboy # key: BBP # condition: (string-prefix-p "/home/kafka/org" (buffer-file-name)) # -- #+TITLE: $1 #+AUTHOR: Nicolas Herry #+DATE: `(format-time-string "%Y/%m/%d")` #+OPTIONS: toc:nil * $1 $0
In the above, I give this snippet a name, beastieboy
, as well as a key that I
will be typing in the buffer to trigger the insertion of this template
(BBP
, which seemed a rare enough combination of letters that any
conflict with a real acronym should be avoided). I also control the
insertion with a condition: yasnippet will only insert the template
if the elisp code in the condition returns non-nil. Here, since I use
org mode for many things, I wanted to contrain this template only to
the posts for this website. An easy way to do this is to check the
path of the buffer being edited. If it contains the directory where
the posts are stored, then the template can be expanded, otherwise,
yasnippet will just do nothing. The drawback here is that I am not
being very subtle, and I have the path hardcoded and not even stored
as a variable or anything. I will do the right thing; for now, it's
good enough.
The template continues with a separator, --
, which indicates that
all that comes after it is the actual meat of the template. We find the
preamble I presented above, with some code to dynamically generate the
date. We also find three odd markers: one $0
and two $1
. The
former indicates where the cursor should be put once the template has
been inserted and filled, and the latter marks field the user must
fill in. When the template is inserted, the cursor will first be
positioned in the line #+TITLE: _
, instead of the $1
, and I will
type in a title for the post. Since I have put more than one $1
marker, yasnippet will automatically update the other markers with the
same number with my typing. This trick allows me to store the title as
a property for org mode as well as a header, without having to type
it twice. When I'm done filling this field, pressing TAB
takes me to
the next field, or, if there aren't any, to where the $0
is. I can
then start typing the post.
Publishing
I publish the site in two steps, and everything here is once again done with emacs. I have defined two sets of projects for org mode in my configuration:
(setq org-publish-project-alist `( ("org" :base-directory "~/org/beastieboy.net" :publishing-directory "~/beastieboy.net/" :publishing-function org-html-publish-to-html :recursive t :section-numbers nil :with-toc nil :base-extension "org" :html-head ,beastieboy-header :html-preamble ,beastieboy-preamble :html-postamble ,beastieboy-footer) ("org-images" :base-directory "~/org/beastieboy.net/images" :publishing-directory "~/beastieboy.net/images/" :base-extension "png\\|jpg\\|gif" :publishing-function org-publish-attachment :recursive t) ("org-js" :base-directory "~/org/beastieboy.net/js" :publishing-directory "~/beastieboy.net/js/" :base-extension "js" :publishing-function org-publish-attachment :recursive t) ("org-css" :base-directory "~/org/beastieboy.net/css" :publishing-directory "~/beastieboy.net/css/" :base-extension "css" :publishing-function org-publish-attachment :recursive t) ("org-remote" :base-directory "~/org/beastieboy.net" :publishing-directory "/ssh:beastieboy@beastieboy.net:/usr/local/www/beastieboy.net/" :publishing-function org-html-publish-to-html :recursive t :section-numbers nil :with-toc nil :base-extension "org" :html-head ,beastieboy-header :html-preamble ,beastieboy-preamble :html-postamble ,beastieboy-footer) ("org-images-remote" :base-directory "~/org/beastieboy.net/images" :publishing-directory "/ssh:beastieboy@beastieboy.net:/usr/local/www/beastieboy.net/images/" :base-extension "png\\|jpg\\|gif" :publishing-function org-publish-attachment :recursive t) ("org-js-remote" :base-directory "~/org/beastieboy.net/js" :publishing-directory "/ssh:beastieboy@beastieboy.net:/usr/local/www/beastieboy.net/js/" :base-extension "js" :publishing-function org-publish-attachment :recursive t) ("org-css-remote" :base-directory "~/org/beastieboy.net/css" :publishing-directory "/ssh:beastieboy@beastieboy.net:/usr/local/www/beastieboy.net/css/" :base-extension "css" :publishing-function org-publish-attachment :recursive t) ("beastieboy" :components ("org" "org-images" "org-js" "org-css")) ("beastieboy-remote" :components ("org-remote" "org-images-remote" "org-js-remote" "org-css-remote"))))
The first set comprises org
, org-images
, org-js
and
org-css
. They all point to different locations in the tree where
I store all my data for this website. The first one deals with the
posts, the next three configure org mode to just copy the files
verbatim to their destination. All this is then regrouped under the
composite project beastieboy
. I use this first set of projects as a
kind of pre-production: I generate the site locally on my PC, and I
check how it looks, proofread the post, and so on.
When I am happy with what I have, I trigger the publication of the
second set of projects, those ending with -remote
and grouped under
beastieboy-remote
. The configuration is identical, except for the
fact that the destination folder is an ssh path to my server. org
mode is then kind enough to call tramp, the emacs mode for
transparently accessing remote files, as the documentation says. In my
case, tramp pushes everything recursively to my server, and the
website is published.
org mode allows the user to specify headers and footers for the
generated pages, which is how I hook up the CSS bits to the
articles. My configuration, in a dedicated .el
file under ~/.emacs.d/
,
is straightforward:
;; custom header, footer, etc. (defvar beastieboy-header "<link rel=\"stylesheet\" href=\"css/site.css\" type=\"text/css\"/>") (defvar beastieboy-preamble "<div class=\"intro\"> <h1><b>Beastie</b>Boy</h1> <p>FreeBSD, Lisp, Emacs, PostgreSQL & co.</p> </div> <div class=\"nav\"> <ul> <li><a href=\"index.html\">Home</a></li> <li><a href=\"about.html\">About</a></li> <li><a href=\"contact.html\">Contact</a></li> </ul> </div>") (defvar beastieboy-footer "<div class=\"footer\"> © 2017 %a.<br/> Created %d.<br/> Last updated %C. <br/> Built with %c.<br/> <img src=\"images/powered-freebsd.gif\" alt=\"Powered by FreeBSD\"/> </div>")
In the above, I set the author's name to that found in the
properties of the post (the #+AUTHOR:
bit, called with %a
here),
the creation date for the post (taken from #+CREATED
in the
preamble, called with %C
), the date and time the page was generated
(called with %d
), I indicate the version of emacs and org mode
that I used to generate the page and I put a nice little blinking
Beastie to show my love to the daemon.
The website itself is handled by nginx, as a set of static HTML
files. Since there is no dynamic part or anything, the configuration
is kept to a minimum: a single line in nginx.conf
to point to the
document root.
What's coming/missing
In my trying to keep things as simple as possible, I also left aside a few things that I arguably should look into: HTML5 support, a proper mobile CSS, code-colouring in the examples, an index generated automatically (today, I still copy and paste all the titles in the index page), a better index with a split between short news and longer articles, an integration with flyspell, the minor mode for spell-checking in emacs. I keep you posted.