Hakyll and vapaus.org

Vapaus.org is generated with Hakyll, which is a Haskell library for generating static sites. Mostly Vapaus.org is just a pile of static HTML files, but there are some more dynamic parts. For example, this page is generated from a Markdown file and the stylesheets are processed with Sass.

Many Hakyll users have published the sources for their websites. I haven’t, because I distantly remember that in my Git repo there might be some personal stuff that shouldn’t be published, but I haven’t bothered to go through the history.

Still, I’m want to share a few patterns from my main.hs. I’ve found them useful and judging by the questions on #hakyll, I’m not the only one.

Static files

If you have a big pile of static files, here are some helpers. Use static to handle individual files and use directory to apply it recursively to all files under a directory.

static :: Pattern -> Rules ()
static f = match f $ do
    route   idRoute
    compile copyFileCompiler

directory :: (Pattern -> Rules a) -> String -> Rules a
directory act f = act $ fromGlob $ f ++ "/**"

-- Here's an example:
main = hakyll $ do
    mapM_ static ["humans.txt", "robots.txt"]
    mapM_ (directory static) ["static/images", "algebra"]

.htaccess

By default, Hakyll ignores all files whose name begins with a dot. However, I wanted to include a .htaccess file to set some MIME types and do some redirects. You can tell Hakyll to allow certain dotfiels by using hakyllWith instead of hakyll:

main = hakyllWith conf $ do
    static ".htacess"
  where
    myIgnoreFile ".htaccess" = False
    myIgnoreFile path        = ignoreFile defaultConfiguration path
    conf = defaultConfiguration { ignoreFile = myIgnoreFile }

Git commit information

Like everyone else1, I store the source for this site in Git. If you scroll to the end of this page, you’ll notice a line with the hash, date and commit message of the last Git commit. I’m using it as a glorified last-modified stamp, although I’m not sure if it’s useful. If I had published my repo, I’d link to GitHub, so everyone could check the history of the page from there.

Useful or not, here’s how you generate it.

import Control.Applicative ((<$>))
import Data.Char           (isSpace)
import Data.List           (dropWhileEnd)
import Data.Monoid         ((<>))
import System.Process      (readProcess)

getGitVersion :: FilePath -> IO String
getGitVersion path = trim <$> readProcess "git" ["log", "-1", "--format=%h (%ai) %s", "--", path] ""
  where
    trim = dropWhileEnd isSpace

-- Field that contains the latest commit hash that hash touched the current item.
versionField :: String -> Context String
versionField name = field name $ \item -> unsafeCompiler $ do
    let path = toFilePath $ itemIdentifier item
    getGitVersion path

-- Field that contains the commit hash of HEAD.
headVersionField :: String -> Context String
headVersionField name = field name $ \_ -> unsafeCompiler $ getGitVersion ""

-- Example usage. Add `$versionField$` to your template.
main = hakyll $ do
    let versionContext = versionField "versionInfo" <> defaultContext
    match "*.markdown" $
        route     setExtension "html"
        compile $ pandocCompiler
            >>= loadAndApplyTemplate "templates/default.html" versionContext
            >>= relativizeUrls

Compiling CoffeeScript

How to compile and minimize CoffeeScript? With two unixFilters.

coffee :: Compiler (Item String)
coffee = getResourceString >>= withItemBody processCoffee
  where
    processCoffee = unixFilter "coffee" ["-c", "-s"] >=>
                    unixFilter "yuicompressor" ["--type", "js"]

Formatting mathematics

If you want to have nicely-formatted math in your blogposts, the simplest way to go is MathJax. Pandoc supports it, but you have to enable it with pandocCompilerWith. Here’s an example:

main = hakyll $ do
    -- MathJax's argument is not used when using Pandoc in Hakyll
    let pandocOptions = defaultHakyllWriterOptions { writerHTMLMathMethod = MathJax "" }
    match "*.markdown"
        route $ setExtension "html"
        compile $ pandocCompilerWith defaultHakyllReaderOptions pandocOptions
            >>= loadAndApplyTemplate "templates/math.html" textContext
            >>= relativizeUrls

You also need to load MathJax’s JavaScript in your template. You can use MathJax CDN or you can choose your own copy of MathJax. If you choose to use the CDN, add this to your <head></head> block:

<script type="text/javascript"
  src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>

Now you can write mathematics inside dollar signs, just like in LaTeX. Here’s an example and the result:

$$ \int_0^r 2 \pi r \mathrm{d}r = \pi r^2 $$

\[ \int_0^r 2 \pi r \mathrm{d}r = \pi r^2 \]

And more…

That’s about it. If you have anything to ask about Hakyll, come to the IRC channel #hakyll on freenode.


  1. Well, not really. For example, Gwern uses Darcs to manage their Hakyll-generated homepage. I would like to take this occassion to point out that Gwern’s site has lots of interesting stuff, which you should check out.↩︎

Revision: 9447469e (2013-02-02 21:43:31 +0200) Write about Hakyll configuration.