Production Drafts for Hakyll Posts

Posted on May 25, 2020 by Riccardo.

Last week I was asked to review a draft of a blog post:

That made me realize I don’t have a way to do a similar trick. However, last week I worked on “Adding published to Hakyll Posts”. Thus, I already did the hard work on understanding the internals, so I could just sit back and code it.

Up until now the blog worked as follows:

  • Published posts were compiled by Hakyll to posts/*.
  • Drafts were not compiled except when HAKYLL_ENV was set to development.

With the new changes:

  • All posts are compiled by Hakyll.
  • Published posts are compiled to posts/* and appear in the archive.
  • Drafts are compiled to drafts/posts/* but do not appear in the archive.

In particular, only published posts are compiled to posts/*:

- matchMetadata "posts/*" (isDevelopmentOrPublished env) $ do
+ matchMetadata "posts/*" isPublished $ do
    route $ setExtension "html"

Drafts are compiled to drafts/posts/*. Also, when compiling the URL to the draft is printed:

matchMetadata "posts/*" (not . isPublished) $ do
  let draftPath = ("drafts/" <>) . (`replaceExtension` "html") . toFilePath
  route . customRoute $ draftPath
  let putDraftUrl path =
        traverse_
          (unsafeCompiler . putStrLn)
          [ "----DRAFT----",
            (previewUrl <>) . draftPath . itemIdentifier $ path,
            "-------------"
          ]
  compile $
    pandocCompiler
      >>= loadAndApplyTemplate "templates/post.html" postCtx
      >>= loadAndApplyTemplate "templates/default.html" postCtx
      >>= relativizeUrls
      >>= (\x -> putDraftUrl x >> pure x)

In the archive only published posts are included except when in development:

  create ["archive.html"] $ do
    route idRoute
    compile $ do
-     posts <- recentFirst =<< loadAll "posts/*"
+     posts <- recentFirst =<< loadAllPublished env "posts/*"
loadAllPublished :: (Binary a, Typeable a) => [(String, String)] -> Pattern -> Compiler [Item a]
loadAllPublished env pattern_ = if isDevelopmentEnv env then all else published
  where
    all = loadAll pattern_
    published = publishedIds pattern_ >>= traverse load
    isDevelopmentEnv env = lookup "HAKYLL_ENV" env == Just "development"

A similar change appears in the Atom feed code:

- =<< loadAllSnapshots "posts/*" "content"
+ =<< loadAllSnapshotsPublished "posts/*" "content"
loadAllSnapshotsPublished :: (Binary a, Typeable a) => Pattern -> Snapshot -> Compiler [Item a]
loadAllSnapshotsPublished pattern_ snapshot = publishedIds pattern_ >>= traverse (`loadSnapshot` snapshot)

publishedIds :: MonadMetadata m => Pattern -> m [Identifier]
publishedIds = fmap (fmap fst . filter (isPublished . snd)) . getAllMetadata

As always, feel free to go ahead and grab the code.


The draft I ended up reviewing was “Functional Fika — Nix and Haskell”. Thanks Maxfield for the inspiration and for teaching me what fika is! ☕️


Support my work by tweeting this article! 🙏