hakyll icon indicating copy to clipboard operation
hakyll copied to clipboard

Hakyll gets confused with list fields if the YAML metadata isn't consistent

Open beerendlauwers opened this issue 7 years ago • 2 comments

Prelude

For HaskAnything.com, I have an authors field. Sometimes, it has data like this:

authors: Beerend Lauwers

And sometimes, it has data like this:

authors:
 - Beerend Lauwers

The context I pass to my templates can be simplified to the following:

templateCtx = metadataListField <> defaultContext

where metadataListField is a custom context I wrote myself that maps a YAML list field to a Hakyll list field, failing to yield a Hakyll field if no such YAML list field could be found:

--------------------------------------------------------------------------------
-- | Map any list field to its metadata value, if present
metadataListField :: Context a
metadataListField = Context $ \k _ i -> do
   values <- getMetadataListField (itemIdentifier i) k
   case values of
     Just vs -> do
                 listItems <- mapM makeItem vs
                 return $ ListField (field "item" (return.itemBody)) listItems
     Nothing -> empty

Problem

The problem I get is that if if have inconsistent YAML data (as explained above), referencing $authors$ in a template will result in an error:

Using YAML:

authors:
 - Beerend Lauwers
[ERROR] Hakyll.Web.Template.applyTemplateWith: expected StringField but got ListField for expr authors

Trying to reference it as a list field with $for(authors)$ $item$ $endfor$ results in the opposite problem for another file:

Using YAML:

authors: Beerend Lauwers
[ERROR] Hakyll.Web.Template.applyTemplateWith: expected ListField but got StringField for expr authors

In the second file, the authors key could be interpreted as a string field or a list field (from Hakyll's perspective), but it is interpreted as a string field.

Solution

First off, perhaps it would be a good idea to automatically expose true YAML list fields as a Hakyll ListField by modifying metadataField to not always return a StringField, but a ListField as well. Here's the current definition:

-- | Map any field to its metadata value, if present
metadataField :: Context a
metadataField = Context $ \k _ i -> do
    value <- getMetadataField (itemIdentifier i) k
    maybe empty (return . StringField) value

Second, perhaps we should document this caveat somewhere, or, even better, error out when we're parsing the YAML metadata of several files and notice an inconsistency like this.

beerendlauwers avatar Jan 16 '17 08:01 beerendlauwers

Yes, I think you are right. The reason for this behavior is that Hakyll hasn't supported YAML for too long -- it used to be just key: value Metadata.

In order to preserve backwards compatibility, I think we could add another primitive like metadataField, but one which understands YAML and tries to do the right thing in most cases, also supporting $for$ loops and data inside that and so on. This could be called yamlMetadataField. What do you think?

jaspervdj avatar Jan 17 '17 12:01 jaspervdj

Sounds good. I already have some code for turning YAML lists into Hakyll lists here: https://github.com/beerendlauwers/HaskAnything/blob/master/src/HaskAnything/Internal/Field.hs#L133

We could have a function that returns Maybe Either a String or a [String], pattern match on that and return a StringField or ListField accordingly.

Bonus points if we make the ListField path recursive, so we can have nested YAML lists :)

I'll see if I can whip up a PR this week.

beerendlauwers avatar Jan 17 '17 13:01 beerendlauwers