summaryrefslogtreecommitdiff
path: root/web/tutorials/04-compilers.markdown
blob: a1fca3731056fdcaa16c9e6d386399cc701b8e29 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
---
title: 'More on compilers: load, and templates'
author: Jasper Van der Jeugt
---

Loading items
-------------

The compiler Monad is a complex beast, but this is nicely hidden for the user of
the Hakyll library.

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
about any of that: Hakyll will sort this out for you automatically!

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
```

This is all useful if we want to use Hakyll's templating system.

Templates
---------

### Basic templates

Let's have a look at a simple template:

```html
<h1>$title$</h1>
<div class="info">Posted on $date$</div>
$body$
```

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 `$$`.

You usually compile the templates from disk using the aptly named
`templateBodyCompiler`:

```haskell
match "templates/*" $ compile templateBodyCompiler
```

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

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
    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
    ]
```

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:

- `$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.

`$date$` is not provided by default. In the scaffold, we use the convenience
context function `dateField`, which will parse an `Item`'s filename to check if
it begins with a date. You can see how we add it in the definition of `postCtx`
in `site.hs`:

```haskell
postCtx :: Context String
postCtx =
    dateField "date" "%B %e, %Y" `mappend`
    defaultContext
```

### Loading and applying templates

Now we know about templates, context and how to load arbitrary items. This gives
us enough background information in order to understand how you can apply a
template:

```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
```

Control flow in templates
-------------------------

Sometimes string interpolation does not suffice, and you want a little more
control over how your templates are layed out. Hakyll provides a few control
structures for this. The syntax for these structures was based on the syntax
used in pandoc templates, since Hakyll already has tight integration with
pandoc.

### Conditionals

In `templates/post.html` of the example site we generated using `hakyll-init`,
we see an example of a conditional:

```html
<div class="info">
    Posted on $date$
    $if(author)$
        by $author$
    $endif$
</div>
```

This example should be pretty straightforward. One important thing to notice is
that `$if(foo)$` **does not** check the truth value of `$foo$`: it merely checks
if such a key is present.

Note that an if-else form is supported as well:

```html
<div class="info">
    Posted on $date$
    $if(author)$
        by $author$
    $else$
        by some unknown author
    $endif$
</div>
```

### Partials

Partials allow you to [DRY] up your templates by putting repetitive actions into
separate template files. You can then include them using
`$partial("filename.html")$`.

[DRY]: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself

An example can be found in `templates/archive.html`:

```html
Here you can find all my previous posts:
$partial("templates/post-list.html")$
```

This partial is just another template and uses the same syntax. Note that in
order to use something like this, we also need to load the partial template in
our `site.hs`:

```haskell
match "templates/post-list.html" $ compile templateCompiler
```

Fortunately, we usually don't need to add this since we already have:

```haskell
match "templates/*" $ compile templateCompiler
```

### Producing a list of items: for

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`. Let's look at the `templates/post-list.html` template:

```html
<ul>
    $for(posts)$
        <li>
            <a href="$url$">$title$</a> - $date$
        </li>
    $endfor$
</ul>
```

This uses the `$for(foo)$` construct. This construct allows you loop over a
list, in this case, `$posts$`. Inside the body of this for loop, all fields
refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.

You can also add a simple separator with the special `$sep$` field. 
Everything between `$sep$` and `$endfor$` will be regarded as a separator
that will only be shown if there is more than one item in the list.

```html
<ul>
    $for(posts)$
        $x$
        $sep$,
    $endfor$
</ul>
```


Of course, $posts$ does not magically appear. We have to specify this in
`site.hs`. Let's look at how `archive.html` is generated:

```haskell
posts <- recentFirst =<< loadAll "posts/*"
let archiveCtx =
        listField "posts" postCtx (return posts) `mappend`
        constField "title" "Archives"            `mappend`
        defaultContext
```

We discussed `loadAll` earlier in this tutorial.

`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.

```haskell
recentFirst :: [Item a] -> Compiler [Item a]
```

After loading and sorting the items, we use `listField` to create the `$posts$`
key.

```haskell
listField :: String -> Context a -> Compiler [Item a] -> Context b
```

The first parameter is simply the name of the key (`"posts"`). Secondly we have
a `Context` with which all items should be rendered -- for our example site, we
already wrote such a `Context` for posts: `postCtx`. Lastly, we have a
`Compiler` which loads the items. We already loaded the items so we can just use
`return posts`.

The following snippet would produce the same result:

```haskell
let archiveCtx =
        listField "posts" postCtx (recentFirst =<< loadAll "posts/*") `mappend`
        constField "title" "Archives"                                 `mappend`
        defaultContext
```