Production Drafts for Hakyll Posts

riccardoodone profile image Riccardo Odone Updated on ・2 min read

You can keep reading here or jump to my blog to get the full experience, including the wonderful pink, blue and white palette.

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 =
          (unsafeCompiler . putStrLn)
          [ "----DRAFT----",
            (previewUrl <>) . draftPath . itemIdentifier $ path,
  compile $
      >>= 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
    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! ☕️

Get the latest content via email from me personally. Reply with your thoughts. Let's learn from each other. Subscribe to my PinkLetter!

Posted on by:

riccardoodone profile

Riccardo Odone


🏳️‍🌈 Pronoun.is/he 💣 Maverick & Leader @Lunar_Logic 🧑‍💻 Functional Programming Rambler 🔥 Sometimes failing 🚀 Sometimes succeeding 💡Always learning


markdown guide