Adding Drafts to Hakyll

April 20, 2014 · 2 minute read

By default, Hakyll does not come with a system for writing drafts: posts whose contents you would like to preview, but not deploy to the live site.

When I first wanted to add drafts to my own website, I did some internet research and found this post by Jorge Israel Peña, which does a great job of explaining what to do. Unfortunately, I assume he was using an older version of Hakyll because I still needed to do some tweaks to make everything work. Here’s how my drafts system works with Hakyll

  1. Put all published posts into the posts directory, and put draft posts into the drafts directory.

  2. To build a draft version of the website, run ./site build draft. To build the deployable version of the website, just run ./site build as normal. Any action can be targeted for the draft version simply by adding draft as the second command line argument. For example, run ./site watch draft to host the draft version of the website on your local machine.

  3. To publish a draft, simply move it from the drafts directory to the posts directory.

Here’s the game plan. In the main function, we first parse the command line arguments before the call to the hakyll function. If we detect the draft parameter, then we’ll modify the Hakyll configuration to use a separate set of cache and destination directories used exclusively for draft builds. We’ll also create a new Pattern called postsPattern that we use anywhere we used to have a pattern for the posts directory: (e.g., match "posts/*" becomes match postsPattern). Here’s the relevant code:

main = do
    args <- getArgs
    let draftMode = length args == 2 && args !! 1 == "draft"
        hakyllConf = if draftMode
                     then defaultConfiguration {
                            destinationDirectory = "_draft",
                            storeDirectory       = "_draftcache",
                            tmpDirectory         = "_draftcache/tmp"
                     else defaultConfiguration
        postsPattern = if draftMode
                       then "posts/*.markdown" .||. "drafts/*.markdown"
                       else "posts/*.markdown"
        args' = take 1 args

    when draftMode $ putStrLn "Running in draft mode"
    withArgs args' $ hakyllWith hakyllConf $ do
        -- Your Hakyll rules go here
        -- ...

Notice that we use withArgs to hide the draft command line argument from the call to hakyllWith because it is not a recognized argument and would cause a command line parsing error.

Finally, as already mentioned, anywhere you attempt to match against a pattern like "posts/*", replace the pattern with postsPattern. Consider defining a utility function

matchPosts = match postsPattern

and using matchPosts throughout your code.