Hakyll’s functionField
function is not documented anywhere, but it is pretty neat. Let’s say you have this in a Hakyll template somewhere:
$translate("greetings")$
That will get parsed as a function call to a Context a
that expects one or more String
arguments. We can define such a Context a
with functionField
:
functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
Here’s a very simple example I used:
postCtx :: Context String
postCtx = functionField "testing" (\args _ -> error $ show args) <> defaultContext
Putting the snippet below in a template and running it:
$testing("hello","test")$
Result:
[ERROR] ["hello","test"]
Even better is that we can also pass other contexts:
$testing("hello","test",title)$
Result:
[ERROR] ["hello","test","Hakyll's functionField"]
In fact, we can pass in functions as well:
$testing("hello","test",testing("hi"))$
Result:
[ERROR] ["hi"]
Unfortunately, that’s all we can do, because of the recursive call to applyExpr
in the template processing module:
applyExpr :: TemplateExpr -> Compiler ContextField
applyExpr (Ident (TemplateKey k)) = context' k [] x
applyExpr (Call (TemplateKey k) args) = do
args' <- mapM (\e -> applyExpr e >>= getString e) args
context' k args' x
applyExpr (StringLiteral s) = return (StringField s)
functionField
allows us to introduce a custom processing function into our templates. For example, I want to relativize a URL in some JavaScript code, and the relativizeURLs
function only focuses on specific tag attributes in HTML code.
With functionField
, I’m able to expose that functionality everywhere:
relativizeUrl :: Context a
relativizeUrl = functionField "relativizeUrl" $ \args item ->
case args of
[k] -> do route <- getRoute $ itemIdentifier item
return $ case route of
Nothing -> k
Just r -> rel k (toSiteRoot r)
_ -> fail "relativizeUrl only needs a single argument"
where
isRel x = "/" `L.isPrefixOf` x && not ("//" `L.isPrefixOf` x)
rel x root = if isRel x then root ++ x else x
-- Add the functionality to our default context
postCtx :: Context String
postCtx = relativizeUrl <> defaultContext
Calling $relativizeUrl("/a/b/c/d")$
then results in a relativized URL in the compiled document. This function is available in my hakyll-extra
package on Github.