Generating this website part 1
Introduction
This is part one of the “generating this website” series. To read the rest of the series, go to the series index here
I’ve recently migrated this website to a new server, and while I was at it I thought I’d have another shot at giving it a reboot and actually keeping it up-to-date this time. I’m much more experienced in both Haskell and literate programming than I was when last I updated it, so when I was modifying my configuration it suddenly occurred to me that since this site is generated using Haskell, and since Haskell supports literate programming natively, I could very easily make the site “self-hosting” by writing up the configuration itself as a series of blog posts!
Thus begins this series, unimaginatively named Generating this Website. To recap, the site is generated using Hakyll, a Haskell library for generating static websites. I will write this series assuming basic Haskell knowledge; you should be familiar with standard typeclasses like Monoid
and Applicative
. Specific knowledge of Hakyll is not required, and if an idiom feels at all obscure I will attempt to explain it as I go. If there’s anything you don’t get, you can always drop me a tweet @danielpwright!
This post will just cover the basics – for details of the generation of actual posts, the index pages, and so on, check back for future posts! I will update this post with links as I write them.
Preliminaries
To begin with, a couple of lines which will grow familiar as this series progresses:
|
|
The OverloadedStrings
LANGUAGE pragma is especially convenient and to be honest I wish it was just defined by default, since I end up including it in nearly any file I write. You can set this in the cabal
file for your project, but if I’m going to make use of LANGUAGE pragmas I’d rather be explicit so I’ll just include them in every file. After that, of course, is the Hakyll import. I’m importing this unqualified in order to make use of the EDSL Hakyll supplies.
I’m also going to import liftA2
from Control.Applicative
here. I use this as a convenience later.
|
Finally, I’ll import the other posts in this series. These supply much of the actual functionality.
|
|
|
|
|
Some simple rules
The main entry point to Hakyll takes a set of Rules
and returns an IO
action which generates the site. Rules
themselves form a monad, so assuming we have some simple rules:
|
We can put them together by simply listing them using do
-notation.
|
|
|
|
|
|
|
|
slideshows
rule above will be defined in future blog posts
The rules themselves govern the compilation and generation of files. Perhaps the simplest of these is templates
, which compiles all files found in the templates
directory and any subdirectories, but doesn’t actually need to output those files anywhere – instead it keeps the compiled versions around for other pages using that template.
|
Hakyll provides a Pattern
type which, conveniently, implements IsString
so our OverloadedStrings
pragma takes care of the conversion for us. The **
pattern searches in that directory and all subdirectories.
Next up come the images. These are also very simple – simply take the full path of the images, and copy them to the same place in the output directory.
|
|
|
The route
rule defines the output filename. idRoute
, as the name implies, sets the output filename to match the input filename. Any rule which generates output requires a route
– any rule without a route
will be run, but won’t generate any output (like the templates
rule above).
For CSS files, Hakyll provides a compressor to speed download times.
|
|
|
Of course, the copyFileCompiler
would work just as well, but we might as well compress the CSS while we’re at it.
Occasionally, I just want to put some static files up that don’t fit the structure of the rest of the blog. This is particularly useful when I want to upload slides from a talk I’ve given, for example the git talk I gave a couple of months ago. The talk itself is maintained in a different repository, so it’s convenient if I can just include that as a submodule and have its files copied automatically. I do this by storing all such content in the static
directory, and then copying it when generating the site, stripping the initial static/
from the output path.
|
|
|
gsubRoute
is actually quite powerful, allowing us to change our substitution based on the matched input, but in this case we just want to substitute for the empty string every time, so we use const
to drop the argument.
Tags, and the Rules
which require them
The remaining rules are complicated by the fact that they need access to the tags for various reasons – the tag index pages obviously need to list all posts matching a certain tag, while the posts themselves and the Atom feed list the tags for a particular post at the bottom of the post.
In order to do this, we first need to generate the tags for the site, and then we need to pass these into those Rules
that make use of them. Generating the tags is quite easy:
|
|
Here I use buildTags
to get the tags from all files in the posts
directory. The default method of tagging posts is just to include a tags
field in the post’s metadata, but if I wanted to do it some other way I could use buildTagsWith
instead.
fromCapture
acts sort of like a Pattern
in reverse; it fills in the capture (The *
in tags/*.html
in this case) with a given string. We use that to say, “for every tag read from the posts’ metadata, create an index page at ‘tags/TAGNAME.html’”.
Having generated the tags, we need to pass them into any rules that need them. We could use do
-notation as we did for simpleRules
and simply pass the tags
parameter to each entry, but here I’m going to use a little Applicative
trick which allows me to keep the function point-free, and I think makes it read a little more declaratively.
|
|
|
This trick exploits the fact that (->)
, the type of functions, implements Applicative
(in fact being applicative is rather their raison d’être when you think about it), so if we lift the Monadic (>>)
operator to act on applications of functions returning a Monad instead of just Monads, we can pass the parameter to the function in once and it will be distributed to each of those functions. In other words:
|
|
Because of Haskell’s function currying and η-reduction, we can then drop the tags
parameter and the brackets entirely and we’re left with the definition for taggedRules
above.
Putting it all together
Finally we define the entry point to the application. This simply calls Hakyll’s own hakyll
function, passing in the rules defined above. First we call the simple, self-standing rules, then we generate the tags and pass them to the tagged rules.
|
|
|
|
This concludes the introduction to Hakyll and the entry point for the generation code for this website. Stay tuned for the next entry, where we’ll add the configuration to actually create the posts themselves!