Blogging in Haskell

It's taken me quite a while to settle on a particular look and feel for this blog. Rather than just having an outlet for writing, I wanted the creation of it to be a learning experience too. Hugo1, Gatsby2 and Zola3, with Netlify CMS4 as a fancy interface for writing posts on top of it all. Each attempt left me feeling less inspired than the last.

Eventually I stumbled across Hakyll5 and, after finding a CSS 'framework' that gave the appearance of a terminal UI6, I felt like I had enough to get things off the ground.

The major appeal so far has been the immense ease of customisation. Hakyll itself isn't a static site generator in the same sense that others are, and as a result it offers a layer of customisation that other generators generally defer to templating languages for.

The main difference is that you don't pull down a hakyll binary and then throw a yaml file together in order to configure a few pre-defined properties; you're instead given a basic implementation of a generator, using hakyll's own library, and thus have complete control over routing, page generation, templating, and so on. This generally lives in a site.hs file and it's not difficult to follow even for relative newbies to Haskell. The structure of everything else is entirely up to you.

Once you compile this file, you end up with a nice binary, e.g. site, and that is what you use to generate your site. It is beautiful in its elegance and I'm eager to see what I can add to this site while also learning some more Haskell at the same time.

As an example, on the home page, there is a git log output section. It's fairly primitive, although I intend to build out the functionality a bit more. Writing the functionality was fairly effortless, with the help of some other authors on the net:

data GitLog = Hash | Commit | Full
deriving (Eq, Read)

instance Show GitLog where
show content = case content of
Hash -> "%h"
Commit -> "%h: %s"
Full -> "%h: %s (%ai)"

getGitLog :: GitLog -> Integer -> FilePath -> IO [String]
getGitLog content limit path = do
(status, stdout, \_) <- readProcessWithExitCode
"git"
[ "log"
, "--format=" ++ show content
, "--max-count=" ++ show limit
, "--"
, path
]
""

    return $ case status of
      ExitSuccess -> splitOn "\n" (trim stdout)
      _           -> [""]
    where trim = dropWhileEnd isSpace

logListField
:: String -> String -> GitLog -> Integer -> String -> Context String
logListField pluralName singularName style limit path =
listField pluralName ctx $ unsafeCompiler $ do
logs <- getGitLog style limit path
return $ map logItem logs
where
ctx = field singularName (return . show . itemBody)
logItem log = Item (fromString $ path ++ "/log/" ++ log) log

The result of adding this code, and then inserting it into the template context, is that I have a new template variable that I can loop over, for each log item. The practical use is fairly limited, but I like it because it adds a certain flavour to the site. Later on I will try to use a parser combinator library to be able to present the different parts of the log with more control.

In any case, I've enjoyed playing around with Haskell in order to deploy this site, and I'm looking forward to seeing what else I can build with the language. It's truly fascinating.

Footnotes: