Hakyll's functionField

By Beerend Lauwers

Introduction

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

Example

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)

Uses

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.

© September 21, 2015 Beerend Lauwers <beerendlauwers@gmail.com>