2

I’d like to add a field to my Hakyll site’s context. If a certain key is present in the metadata then I’d like to transform the corresponding value and include that in the context. If the key is not present in the metadata then nothing should be added to the context.

I wrote this function that should do what I described:

-- | Creates a new field based on the item's metadata. If the metadata field is
-- not present then no field will actually be created. Otherwise, the value will
-- be passed to the given function and the result of that function will be used
-- as the field's value.
transformedMetadataField :: String -> String -> (String -> String) -> Context a
transformedMetadataField key itemName f = field key $ \item -> do
    fieldValue <- getMetadataField (itemIdentifier item) itemName
    return $ maybe (fail $ "Value of " ++ itemName ++ " is missing") f fieldValue

However, if the metadata field is not present then this will still insert a field into the context with the empty string as its value. For example, I have this line in my context:

transformedMetadataField "meta_description" "meta_description" escapeHtml

and I have this template:

$if(meta_description)$
    <meta name="description" content="$meta_description$"/>
$endif$

On pages with no meta_description in their metadata, the following HTML is produced:

    <meta name="description" content=""/>

whereas what I want is for no tag to be produced at all.

What have I done wrong in my transformedMetadataField function?

Teodor
  • 749
  • 7
  • 15
bdesham
  • 15,430
  • 13
  • 79
  • 123

2 Answers2

3

You need to return Control.Applicative's "empty" to have a field be entirely non-existant. An example from my own code:

-- What we're trying to do is produce a field that *doesn't* match
-- key in the case where the metadata "header" is not set to "no" or
-- "false"; matching it and returning false or whatever
-- (makeHeaderField above) isn't working, so any call to "field" is
-- guaranteed to not work
makeHeaderField :: String -> Context a
makeHeaderField key = Context $ \k _ i -> do
    if key == k then do
      value <- getMetadataField (itemIdentifier i) "header"
      if isJust value then
        if elem (fromJust value) [ "no", "No", "false", "False" ] then
          -- Compiler is an instance of Alternative from
          -- Control.Applicative ; see Hakyll/Core/Compiler/Internal.hs
          CA.empty
        else
          return $ StringField $ fromJust value
      else
        return $ StringField "yes makeheader"
    else
      CA.empty

Oh, I forgot: as my code comments specify above, you can't use the hakyll "field" function in this case, because "field" always treats the field as existant in the case where the field name matches. You have to copy the code from "field" to get your own on CA.empty returns in the places you want them, as I've done above (CA is Control.Applicative).

rlpowell
  • 1,160
  • 1
  • 9
  • 13
  • “ ‘field’ always treats the field as existant in the case where the field name matches” – bless your soul for actually explaining this. Thank you! – bdesham Nov 04 '17 at 17:30
  • This if else nesting does not feel idiomatic. `case value of` would make this more readable. – dedoussis Aug 16 '21 at 10:48
1

For posterity, here is the function I ended up with thanks to @rlpowell’s answer.

-- | Creates a new field based on the item's metadata. If the metadata
-- field is not present then no field will actually be created.
-- Otherwise, the value will be passed to the given function and the
-- result of that function will be used as the field's value.
transformedMetadataField :: String -> String -> (String -> String) -> Context a
transformedMetadataField newKey originalKey f = Context $ \k _ i -> do
    if k == newKey
       then do
           value <- getMetadataField (itemIdentifier i) originalKey
           maybe empty (return . StringField . f) value
       else empty
bdesham
  • 15,430
  • 13
  • 79
  • 123