hakyll
hakyll copied to clipboard
Hakyll gets confused with list fields if the YAML metadata isn't consistent
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.
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?
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.