hakyll/web/tutorials/04-compilers.markdown

179 lines
5 KiB
Markdown
Raw Normal View History

2012-12-15 17:17:12 +00:00
---
2012-12-15 19:01:16 +00:00
title: More on compilers: load, and templates
2012-12-15 17:17:12 +00:00
author: Jasper Van der Jeugt
---
2012-12-15 19:01:16 +00:00
Loading items
-------------
2012-12-15 17:17:12 +00:00
2012-12-15 19:01:16 +00:00
The compiler Monad is a complex beast, but this is nicely hidden for the user of
the Hakyll library.
2012-12-15 17:17:12 +00:00
2012-12-15 19:01:16 +00:00
Suppose that you're generating `index.html` which shows your latest brilliant
blogpost. This requires `posts/foo.markdown` to be generated *before*
`index.html` (so we don't have to generate it twice). But you don't have to care
2012-12-17 10:10:57 +00:00
about any of that: Hakyll will sort this out for you automatically!
2012-12-15 19:01:16 +00:00
Let's see some quick examples. We can load a specific item:
```haskell
load "posts/foo.markdown" :: Compiler (Item String)
```
Or a whole bunch of them:
```haskell
loadAll "posts/*" :: Compiler [Item String]
```
Sometimes you just want the *contents* and not the `Item`:
```haskell
loadBody "posts/foo.markdown" :: Compiler String
```
2012-12-15 23:21:59 +00:00
This is all useful if we want to use Hakyll's templating system.
2012-12-17 16:15:36 +00:00
Templates
---------
### Basic templates
2012-12-16 23:46:23 +00:00
Let's have a look at a simple template:
```html
<h1>$title$</h1>
<div class="info">Posted on $date$</div>
$body$
```
2012-12-16 23:46:23 +00:00
As you can probably guess, template files just contain text and only the `$`
character has special meaning: text between dollar signs ("fields") is replaced
when the template is applied. If you want an actual dollar sign in the output,
use `$$`.
2012-12-17 16:15:36 +00:00
You usually compile the templates from disk using the aptly named
`templateCompiler`:
```haskell
match "templates/*" $ compile templateCompiler
```
2012-12-17 16:15:36 +00:00
Notice the lack of `route` here: this is because we don't need to write the
templates to your `_site` folder, we just want to use them elsewhere.
### Templates: Context
2012-12-16 23:46:23 +00:00
We can easily guess the meaning of `$title$`, `$date$`, and `$body$`, but these
are not hard-coded fields: they belong to a certain [Context]. A `Context`
determines how the fields are interpreted. It's a [Monoid] and therefore very
composable.
[Context]: /reference/Hakyll-Web-Template-Context.html
[Monoid]: http://learnyouahaskell.com/functors-applicative-functors-and-monoids
`field` allows us to create a `Context` for a single field:
```haskell
field :: String -> (Item a -> Compiler String) -> Context a
```
Let's try this out. Note that this is for illustration purposes only: you
shouldn't have to write complicated fields often. We can implement the `$body$`
field like this:
```haskell
field "body" $ \item -> return (itemBody item) :: Context String
```
And `$title$` like this:
```haskell
titleContext :: Context a
titleContext = field "title" $ \item -> do
2012-12-16 23:46:23 +00:00
metadata <- getMetadata (itemIdentifier item)
return $ fromMaybe "No title" $ M.lookup "title" metadata
```
And compose them using the `Monoid` instance:
```haskell
context :: Context String
context = mconcat
[ titleContext
, field "body" $ return . itemBody
]
```
2012-12-17 16:15:36 +00:00
Obviously, it would be tedious to implement things like `titleContext` over and
over again for different websites and different fields. This is why hakyll
provides `defaultContext`. `defaultContext` is a composed `Context` and allows
you to use:
2012-12-16 23:46:23 +00:00
2012-12-17 16:15:36 +00:00
- `$body$` for the body of the page;
- `$url$` for the destination URL of the page;
- `$path$` for the original filepath of the page;
- `$foo$` where foo is specified in the metadata.
2012-12-16 23:46:23 +00:00
2012-12-17 16:15:36 +00:00
`$date$` is not provided by default, you can see how we add it in the definition
of `postCtx` in `site.hs`:
2012-12-16 23:46:23 +00:00
2012-12-17 16:15:36 +00:00
```haskell
postCtx :: Context String
postCtx =
dateField "date" "%B %e, %Y" `mappend`
defaultContext
```
### Loading and applying templates
2012-12-16 23:46:23 +00:00
2012-12-17 16:15:36 +00:00
Now we know about templates, context and how to load arbitrary items. This gives
us enough background information in order to understand you can apply a
template:
2012-12-16 23:46:23 +00:00
2012-12-17 16:15:36 +00:00
```haskell
compile $ do
tpl <- loadBody "templates/post.html"
pandocCompiler >>= applyTemplate tpl postCtx
```
Loading and then immediately applying a template is so common there's a
shorthand function:
```haskell
compile $
pandocCompiler >>= loadAndApplyTemplate "templates/post.html" postCtx
```
## Producing a list of items
At this point, everything in the example site we generated should be clear to
you, except for how we produce the list of posts in `archive.html` and
`index.html`.
However, this really isn't hard and just uses the things we saw before: loading
other items and applying templates.
We can reproduce a list of items in the archive using the following code:
```haskell
compile $ do
posts <- recentFirst =<< loadAll "posts/*"
2012-12-17 16:15:36 +00:00
itemTpl <- loadBody "templates/post-item.html"
2013-01-08 19:14:54 +00:00
list <- applyTemplateList itemTpl postCtx posts
makeItem list
2012-12-17 16:15:36 +00:00
```
`recentFirst` sorts items by date. This relies on the convention that posts are
always named `YYYY-MM-DD-title.extension` in Hakyll -- or that the date must be
present in the metadata.
2012-12-17 16:15:36 +00:00
```haskell
recentFirst :: [Item a] -> Compiler [Item a]
2012-12-17 16:15:36 +00:00
```
2012-12-16 23:46:23 +00:00
2012-12-17 16:15:36 +00:00
After loading and sorting the items, we load a template for the posts.
`applyTemplateList` applies this template to every post and concatenates the
2013-01-08 19:14:54 +00:00
result, which is a simple `String`. After that, we need `makeItem` to wrap the
returned `String` to `Item String`.