1

I'm trying to generate HTML for posts in Hakyll that have a versions entry in their metadata. For example, a post may have versions: Python 3.4, pytest 1.5.2 which would be formatted nicely at the bottom of the post.

To achieve this, I want to create a context which loads the metadata and creates a ListField. Something like the following stub:

versionsCtx :: Context String
versionsCtx = listFieldWith "versions" ctx (\item -> do
    versions <- getMetadataField (itemIdentifier item) "versions"

    return $ case versions of
      Just lst -> map (mkVersinoItem . trim) $ splitAll "," lst
      Nothing  -> [])
          where ctx = field "version" (return . itemBody)
                mkVersionItem version = Item {
                    itemIdentifier = fromString ("version/" ++ version),
                    itemBody = version
                }

In my post.html template, I have:

...
    <section>
        $body$

        $if(versions)$
        <hr />
        <ul>
            $for(versions)$
                <li>$version$</li>
            $endfor$
        </ul>
        $else$
            <p>Fail...</p>
        $endif$
    </section>
...

Yet I have tried many different definitions of versionsCtx and found similar attempts online. None seem to work and the post is always rendered with "Fail...". What am I doing wrong?

EDIT: Updated question with suggestions and clarifications.

NordCoder
  • 403
  • 1
  • 7
  • 21

1 Answers1

4

There are multiple issues with your code:

  1. getMetadataField provides a Maybe type, which in Haskell has data constructors Just and Nothing, not Some and None.
  2. The makeItem function creates an Item already wrapped in a Compiler, resulting in the following error:
• Couldn't match type ‘Compiler (Item String)’ with ‘Item String’
  Expected type: Compiler [Item String]
    Actual type: Compiler [Compiler (Item String)]

While you could try to extract the item from it, it is probably cleaner to create an item from scratch using something like this:

mkVersionItem version = Item {
    itemIdentifier = fromString ("version/" ++ version),
    itemBody = version
}
  1. I do not see you adding the newly created context to the post context. Did you do that?
  2. As mentioned in the docs, the order Contexts are being appended is important. It is not evident from your question, but you are probably using defaultContext, which includes metadataField. You have versions field in the metadata block of your posts so when the defaultContext wins, it will make versions available as a string field in the template. $if(versions)$ for some reason jumps to the else branch when versions is a string field, which explains why “Fail” is shown. You can see a more informative error in the console when you move the for loop outside the conditional block:
[ERROR] Hakyll.Web.Template.applyTemplateWith: expected ListField but got StringField for expr versions

In full the code could look something like this:

import           Data.String (fromString)

postCtx :: Context String
postCtx =
    versionsCtx `mappend`
    dateField "date" "%B %e, %Y" `mappend`
    defaultContext

versionsCtx :: Context String
versionsCtx = listFieldWith "versions" ctx (\item -> do
    versions <- getMetadataField (itemIdentifier item) "versions"

    return $ case versions of
      Just lst -> map (mkVersionItem . trim) $ splitAll "," lst
      Nothing     -> []
    )
  where
    ctx = field "version" (return . itemBody)
    mkVersionItem version = Item {
        itemIdentifier = fromString ("version/" ++ version),
        itemBody = version
    }
Jan Tojnar
  • 5,306
  • 3
  • 29
  • 49
  • Thank you for the suggestions. I've updated my question to better reflect that my `versionsCtx` was a stub. I am adding the `versionsCtx` to the context in the rule for `match "posts/*"`. I got it to work by adding the `versionsCtx` _before_ the `postCtx` instead of _after_ it. The docs say that the order of composition of the `Context` monoid is important as fields can be overwritten but in this case, my `postCtx` does not overwrite anything (contains date, tags etc.), so I am not sure why the order matters here. – NordCoder Aug 14 '19 at 09:24
  • Aha! That explains it. The source for `metadataField` shows how `maybe` is used to create a string field by default. Thanks a lot! I've added a comment in my `site.hs` to make sure I remember in the future. – NordCoder Aug 14 '19 at 15:51