on master help sign in
Add tutorial about templates
commitd3b87ba2590480a05d8b13770889ac1f14a2b573 authorJasper Van der Jeugt <jaspervdj@gmail.com> dateFri 6 Aug 2010, 13:28 (16 years ago) parentd0a9d01
7 files changed+565−522
examples/hakyll/tutorials/part03.markdown+1−1 examples/hakyll/tutorials/part04.markdown+43−140 examples/hakyll/tutorials/part05.markdown+125−52 examples/hakyll/tutorials/part06.markdown+51−188 examples/hakyll/tutorials/part07.markdown+183−64 examples/hakyll/tutorials/part08.markdown+58−77 examples/hakyll/tutorials/part09.markdown+104
examples/hakyll/tutorials/part03.markdown+1−1
@@ -1,6 +1,6 @@
11---
22title: How to write pages
3−what: elaborates a little on writing pages and templates
3+what: elaborates a little on writing pages
44---
55
66## The structure of a Page
examples/hakyll/tutorials/part04.markdown+43−140
@@ -1,157 +1,60 @@
11---
2−title: Creating a Blog
3−what: creates a simple blog
2+title: How to write templates
3+what: more information on template writing
44---
55
6−## Creating a simple blog with Hakyll
6+## Simple templates
77
8−After we created a simple brochure site, we're going to try something more
9−advanced: we are going to create a simple blog system.
8+Simple templates are simply HTML files, with `$identifiers`. An example:
109
11−A [zip file] containing the source for this tutorial is also available.
12−
13−[zip file]: $root/examples/simpleblog.zip
14−
15−Blogs, as you probably know, are composed of posts. In Hakyll, we're going
16−to use simple pages for posts. All posts are located in the `posts`
17−directory. But we're not going to use the `directory` command here - you will
18−see why later. First, some trivial things like css.
19−
20−~~~~~{.haskell}
21−main = hakyll "http://example.com" $ do
22− directory css "css"
23−~~~~~
24−
25−## Finding the posts
26−
27−`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
28−provide us with all the blog posts. The blog posts have a
29−`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
30−can sort them easily (sorting on filename implies sorting on date). You could of
31−course name them whatever you want, but it's always a good idea to stick to the
32−conventions. They contain some metadata, too:
33−
34− title: A first post
35− author: Julius Caesar
36− date: November 5, 2009
37− ---
38− Lorem ipsum dolor sit amet, consectetur adipiscing elit.
39− Vivamus pretium leo adipiscing lectus iaculis lobortis.
40− Vivamus scelerisque velit dignissim metus...
41−
42−Now, we find the posts and sort them reversed, so the most recent post will
43−become the first item in the list:
44−
45−~~~~~{.haskell}
46−postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
47−~~~~~
48−
49−Our `postPaths` value is now of the type `[FilePath]`. We want to be able to
50−render all posts, so we pass them to the `createPage` function.
51−
52−~~~~~{.haskell}
53−let postPages = map createPage postPaths
54−~~~~~
55−
56−We have two templates we want to render our posts with: first we would like to
57−render them using `templates/post.html`, and we want to render the result
58−using `templates/default.html`. This can be done with the `renderChain`
59−function:
60−
61−~~~~~{.haskell}
62−mapM_ (renderChain [ "templates/post.html"
63− , "templates/default.html"
64− ]) postPages
65−~~~~~
66−
67−Remember that the `renderChain` works by rendering the item using the first
68−template, creating a new page with the render result in the `$body` field, and
69−so on until it has been rendered with all templates.
70−
71−Now, we have the posts rendered. What is left is to generate some kind of index
72−page with links to those posts. We want one general list showing all posts, and
73−we want to show a few recent posts on the index page.
74−
75−## Creating listings.
76−
77−`createPage` is the easiest way of reading a `Context`. But in this case, we
78−want something more custom, so we'll use the `createCustomPage` function. This
79−allows us to create a more specific `Context`.
80−
81−~~~~~{.haskell}
82−createCustomPage :: FilePath
83− -> [(String, Either String (HakyllAction () String)]
84− -> HakyllAction () Context
85−~~~~~
86−
87−The first argument is the `url` of the page to generate. For our index page,
88−this will be, `index.html`. The second argument is obviously our `key: value`
89−mapping. But why the `Either`? This, once again, is about dependency handling.
90−The idea is that you can choose which type to use for the value:
91−
92−- `String`: Simply a `String`.
93−- `HakyllAction () String`: Here, you can give an `HakyllAction` Arrow action
94− that can produce a String. However - this action _will not be executed_ when
95− the file in `_site` is up-to-date.
96−
97−However, in this specific case - a list of posts - there is an easier, and more
98−high-level approach than `createCustomPage`[^1]. Let's look at the type
99−signature of `createListing`:
100−
101−~~~~~{.haskell}
102−createListing :: FilePath
103− -> [FilePath]
104− -> [HakyllAction () Context]
105− -> [(String, Either String (HakyllAction () String)]
106− -> HakyllAction () Context
10+~~~~~{.html}
11+<html>
12+ <head>
13+ <title>$title</title>
14+ </head>
15+ <body>
16+ $body
17+ </body>
18+</html>
10719~~~~~
10820
109−[^1]: Since Hakyll-1.3 onwards.
21+## Markup in templates
11022
111−The first argument is the destination url. For our blog, this is of course
112−`index.html`. The second argument is a list templates to render _each_ `Context`
113−with. We use only `templates/postitem.html` here. This is, as you can see, a
114−simple template:
23+Most of the examples in these tutorials use HTML for templates. However, since
24+Hakyll 2.2, it is possible use other markup languages in your templates. Simply
25+use an appropriate extension, and Hakyll will pick it up. For example, you could
26+write your `templates/post.markdown` template as:
11527
116−~~~~~{.html}
117−<li>
118− <a href="$$root/$url">$title</a>
119− - <em>$date</em> - by <em>$author</em>
120−</li>
121−~~~~~
28+ # $title
12229
123−We then give a list of @Context@s to render. For our index, these are the 3 last
124−posts. The last argument of the `createListing` functions lets you specify
125−additional key-value pairs, like in `createCustomPage`. We use this to set the
126−title of our page. So, we create our index page using:
30+ _On $date_
12731
128−~~~~~{.haskell}
129−let index = createListing "index.html"
130− ["templates/postitem.html"]
131− (take 3 postPages)
132− [("title", Left "Home")]
133−~~~~~
32+ $body
13433
135−The result of this will be a `HakyllAction () Context`. This `Context`'s `$body`
136−will contain a concatenation of all the 3 posts, rendered with the
137−`templates/postitem.html` template.
34+__Warning__: you shouldn't use markdown for your "root" template, as these
35+templates will never insert things like the doctype for you -- so you always
36+need at least one top-level HTML template.
13837
139−Now, we only have to render it: first using the `index.html` template - which
140−adds some more information to our index - then using the
141−`templates/default.html` template.
38+## Hamlet templates
14239
143−~~~~~{.haskell}
144−renderChain ["index.html", "templates/default.html"] index
145−~~~~~
40+From Hakyll 2.3 onwards, it is possible to use [hamlet] templates. You can find
41+more information about hamlet on that website. Usage is fairly simple -- since
42+pages are strictly key-value mappings, only `$variable$` control is supported in
43+hamlet templates. As an example, here is the template that can be used for the
44+brochure site, but in hamlet:
14645
147−Note that the `index.html` in the `renderChain` list is also a template. Now,
148−you might want to take your time to read the `index.html` template and the other
149−files in the zip so you understand what is going on here.
46+ !!!
47+ %html
48+ %head
49+ %title MyAweSomeCompany - $$title$
50+ %body
51+ %h1 MyAweSomeCompany - $$title$
52+ #navigation
53+ %a!href="$$root$/index.html" Home
54+ %a!href="$$root$/about.html" About
55+ %a!href="$$root$/code.html" Code
56+ $body$
15057
151−## The gist of it
58+Hakyll will recognise hamlet templates automatically by the `.hamlet` extension.
15259
153−- You can find blogposts using `getRecursiveContents`.
154−- The convention is to call them `yyyy-mm-dd-rest-of-title.extension`. This
155− allows us to sort them easily.
156−- You can use `createCustomPage` or `createListing` to create custom pages and
157− simple listings.
60+[hamlet]: http://docs.yesodweb.com/hamlet/
examples/hakyll/tutorials/part05.markdown+125−52
@@ -1,84 +1,157 @@
11---
2−title: Creating feeds
3−what: adds an rss feed to the simple blog
2+title: Creating a Blog
3+what: creates a simple blog
44---
55
6−## Adding Feeds
6+## Creating a simple blog with Hakyll
77
8−In this tutorial, we're going to add an RSS feed to the blog we wrote in the
9−previous part. Here is a [zip file] containing the source.
8+After we created a simple brochure site, we're going to try something more
9+advanced: we are going to create a simple blog system.
1010
11−[zip file]: $root/examples/feedblog.zip
11+A [zip file] containing the source for this tutorial is also available.
1212
13−You will be glad to hear that Hakyll has native support for RSS as well as Atom
14−feeds[^1]. This simplifies our object a lot.
13+[zip file]: $root/examples/simpleblog.zip
1514
16−[^1]: Since Hakyll-2.0
15+Blogs, as you probably know, are composed of posts. In Hakyll, we're going
16+to use simple pages for posts. All posts are located in the `posts`
17+directory. But we're not going to use the `directory` command here - you will
18+see why later. First, some trivial things like css.
1719
18−This is the first time that the absolute URL of your site you have to give to
19−the `hakyll` function actually matters. That's because the specifications of
20−those feed formats dictate you need the full URL of them.
20+~~~~~{.haskell}
21+main = hakyll "http://example.com" $ do
22+ directory css "css"
23+~~~~~
24+
25+## Finding the posts
26+
27+`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
28+provide us with all the blog posts. The blog posts have a
29+`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
30+can sort them easily (sorting on filename implies sorting on date). You could of
31+course name them whatever you want, but it's always a good idea to stick to the
32+conventions. They contain some metadata, too:
33+
34+ title: A first post
35+ author: Julius Caesar
36+ date: November 5, 2009
37+ ---
38+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
39+ Vivamus pretium leo adipiscing lectus iaculis lobortis.
40+ Vivamus scelerisque velit dignissim metus...
41+
42+Now, we find the posts and sort them reversed, so the most recent post will
43+become the first item in the list:
2144
22−## Creating a configuration
45+~~~~~{.haskell}
46+postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
47+~~~~~
2348
24−The first thing to do is creating a configuration for your feed. You could
25−place this code outside of your main function, as is done in the example.
49+Our `postPaths` value is now of the type `[FilePath]`. We want to be able to
50+render all posts, so we pass them to the `createPage` function.
2651
2752~~~~~{.haskell}
28−myFeedConfiguration = FeedConfiguration
29− { feedUrl = "rss.xml"
30− , feedTitle = "SimpleBlog RSS feed."
31− , feedDescription = "Simple demo of a feed created with Hakyll."
32− , feedAuthorName = "Jasper Van der Jeugt"
33− }
53+let postPages = map createPage postPaths
3454~~~~~
3555
36−Note that we enter the url of the feed in our configuration. So the function
37−to render our feed only takes two arguments, the configuration and a list of
38−items to put in the feed. Let's put the three most recent posts in our feed.
56+We have two templates we want to render our posts with: first we would like to
57+render them using `templates/post.html`, and we want to render the result
58+using `templates/default.html`. This can be done with the `renderChain`
59+function:
3960
4061~~~~~{.haskell}
41−renderRss myFeedConfiguration (take 3 postPages)
62+mapM_ (renderChain [ "templates/post.html"
63+ , "templates/default.html"
64+ ]) postPages
4265~~~~~
4366
44−## But it's not that easy
67+Remember that the `renderChain` works by rendering the item using the first
68+template, creating a new page with the render result in the `$body` field, and
69+so on until it has been rendered with all templates.
70+
71+Now, we have the posts rendered. What is left is to generate some kind of index
72+page with links to those posts. We want one general list showing all posts, and
73+we want to show a few recent posts on the index page.
4574
46−If you look at our generated RSS feed (build the site), you will see
47−`$description` tags appearing in our final render. We don't want that! How
48−did they get there in the first place?
75+## Creating listings.
4976
50−To render feeds, Hakyll expects a number of fields in the renderables you put
51−in the feed. They are:
77+`createPage` is the easiest way of reading a `Context`. But in this case, we
78+want something more custom, so we'll use the `createCustomPage` function. This
79+allows us to create a more specific `Context`.
5280
53−- `$title`: Title of the item. This is set in our posts, since we use a `title`
54− metadata field.
55−- `$url`: Url of the item. This is automatically set by Hakyll, so you shouldn't
56− worry about it.
57−- `$description`: A description of our item to appear in the feed reader.
81+~~~~~{.haskell}
82+createCustomPage :: FilePath
83+ -> [(String, Either String (HakyllAction () String)]
84+ -> HakyllAction () Context
85+~~~~~
5886
59−The latter is obviously the problem: we don't have a description in our posts.
60−In fact, we would like to copy the `$body` key to the `$description` key, so
61−people can read the full post in their feed readers.
87+The first argument is the `url` of the page to generate. For our index page,
88+this will be, `index.html`. The second argument is obviously our `key: value`
89+mapping. But why the `Either`? This, once again, is about dependency handling.
90+The idea is that you can choose which type to use for the value:
6291
63−## Where arrows come in
92+- `String`: Simply a `String`.
93+- `HakyllAction () String`: Here, you can give an `HakyllAction` Arrow action
94+ that can produce a String. However - this action _will not be executed_ when
95+ the file in `_site` is up-to-date.
6496
65−The `Text.Hakyll.ContextManipulations` module contains a number of simple
66−functions that create Arrows for us. One of these functions is `copyValue`,
67−which takes a source and a destination key. So, we need to pass our
68−items through this Arrow first.
97+However, in this specific case - a list of posts - there is an easier, and more
98+high-level approach than `createCustomPage`[^1]. Let's look at the type
99+signature of `createListing`:
69100
70101~~~~~{.haskell}
71−renderRss myFeedConfiguration $
72− map (>>> copyValue "body" "description") (take 3 postPages)
102+createListing :: FilePath
103+ -> [FilePath]
104+ -> [HakyllAction () Context]
105+ -> [(String, Either String (HakyllAction () String)]
106+ -> HakyllAction () Context
73107~~~~~
74108
75−And that's that, now our feed gets rendered properly. Exercise for the reader
76−is to add a Atom feed[^2].
109+[^1]: Since Hakyll-1.3 onwards.
110+
111+The first argument is the destination url. For our blog, this is of course
112+`index.html`. The second argument is a list templates to render _each_ `Context`
113+with. We use only `templates/postitem.html` here. This is, as you can see, a
114+simple template:
115+
116+~~~~~{.html}
117+<li>
118+ <a href="$$root/$url">$title</a>
119+ - <em>$date</em> - by <em>$author</em>
120+</li>
121+~~~~~
122+
123+We then give a list of @Context@s to render. For our index, these are the 3 last
124+posts. The last argument of the `createListing` functions lets you specify
125+additional key-value pairs, like in `createCustomPage`. We use this to set the
126+title of our page. So, we create our index page using:
127+
128+~~~~~{.haskell}
129+let index = createListing "index.html"
130+ ["templates/postitem.html"]
131+ (take 3 postPages)
132+ [("title", Left "Home")]
133+~~~~~
134+
135+The result of this will be a `HakyllAction () Context`. This `Context`'s `$body`
136+will contain a concatenation of all the 3 posts, rendered with the
137+`templates/postitem.html` template.
138+
139+Now, we only have to render it: first using the `index.html` template - which
140+adds some more information to our index - then using the
141+`templates/default.html` template.
142+
143+~~~~~{.haskell}
144+renderChain ["index.html", "templates/default.html"] index
145+~~~~~
77146
78−[^2]: Hint: look around in the [reference]($root/reference.html).
147+Note that the `index.html` in the `renderChain` list is also a template. Now,
148+you might want to take your time to read the `index.html` template and the other
149+files in the zip so you understand what is going on here.
79150
80151## The gist of it
81152
82−- Hakyll has native support for RSS and Atom feeds.
83−- The items must contain `$title` and `$description` fields.
84−- Arrows can be used to copy values in a `Context`.
153+- You can find blogposts using `getRecursiveContents`.
154+- The convention is to call them `yyyy-mm-dd-rest-of-title.extension`. This
155+ allows us to sort them easily.
156+- You can use `createCustomPage` or `createListing` to create custom pages and
157+ simple listings.
examples/hakyll/tutorials/part06.markdown+51−188
@@ -1,221 +1,84 @@
11---
2−title: Tags and manipulations
3−what: enhances our blog with tags and explains context manipulations.
2+title: Creating feeds
3+what: adds an rss feed to the simple blog
44---
55
6−## Context manipulations
6+## Adding Feeds
77
8−Here, have [a zip file]($root/examples/tagblog.zip) for this tutorial.
8+In this tutorial, we're going to add an RSS feed to the blog we wrote in the
9+previous part. Here is a [zip file] containing the source.
910
10−You probably remember that `Context` objects are just key-value mappings. We can
11−render those with templates, and then the `$key`'s in the template get
12−substituted by the appropriate values. This is a rather flexible system, but
13−there are limitations. Some of these limitations can be solved using
14−_context manipulations_.
11+[zip file]: $root/examples/feedblog.zip
1512
16−Like rendering actions, _context manipulations_ are also simply
17−`HakyllAction Context Context` arrows. The `Text.Hakyll.ContextManipulations`
18−contains some functions to easily construct easy variants.
13+You will be glad to hear that Hakyll has native support for RSS as well as Atom
14+feeds[^1]. This simplifies our object a lot.
1915
20−One of the most general functions is the `renderValue` function. Let's have a
21−look at it's type.
16+[^1]: Since Hakyll-2.0
2217
23−~~~~~{.haskell}
24−renderValue :: String
25− -> String
26− -> (String -> String)
27− -> HakyllAction Context Context
28−~~~~~
18+This is the first time that the absolute URL of your site you have to give to
19+the `hakyll` function actually matters. That's because the specifications of
20+those feed formats dictate you need the full URL of them.
2921
30−This is the preferred way of creating context manipulations. The first argument
31−is the `key` to manipulate. The second argument is the `key` where the new value
32−should be placed. If this is the same as the first argument, it will be
33−replaced. The third argument is the function to manipulate the `value` with.
22+## Creating a configuration
3423
35−As a simple example, let's write a function that puts the `$title` in uppercase.
24+The first thing to do is creating a configuration for your feed. You could
25+place this code outside of your main function, as is done in the example.
3626
3727~~~~~{.haskell}
38−import Data.Char (toUpper)
39−
40−titleUpper :: HakyllAction Context Context
41−titleUpper = renderValue "title" "title" $ map toUpper
28+myFeedConfiguration = FeedConfiguration
29+ { feedUrl = "rss.xml"
30+ , feedTitle = "SimpleBlog RSS feed."
31+ , feedDescription = "Simple demo of a feed created with Hakyll."
32+ , feedAuthorName = "Jasper Van der Jeugt"
33+ }
4234~~~~~
4335
44−Because the destination `key` is the same as the source `key`, we can also use
45−the `changeValue` function here.
36+Note that we enter the url of the feed in our configuration. So the function
37+to render our feed only takes two arguments, the configuration and a list of
38+items to put in the feed. Let's put the three most recent posts in our feed.
4639
4740~~~~~{.haskell}
48−titleUpper = changeValue "title" $ map toUpper
41+renderRss myFeedConfiguration (take 3 postPages)
4942~~~~~
5043
51−For further reading, refer to the `Text.Hakyll.ContextManipulations`
52−documentation.
53−
54−## Applying Context Manipulations
44+## But it's not that easy
5545
56−Because we're dealing with Arrows again, we can use `>>>` to apply our
57−manipulations. For example, we could use or title manipulation like this:
58−
59−~~~~~{.haskell}
60−renderChain ["templates/default.html"]
61− (createPage "index.markdown" >>> titleUpper)
62−~~~~~
46+If you look at our generated RSS feed (build the site), you will see
47+`$description` tags appearing in our final render. We don't want that! How
48+did they get there in the first place?
6349
64−## Rendering dates
50+To render feeds, Hakyll expects a number of fields in the renderables you put
51+in the feed. They are:
6552
66−As you remember, in our previous blog, all posts had a file name like
67−`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
68−had a metadata field `date`, containing a human-readable date. This is not very
69−D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
70−dates encoded in paths: `renderDate`.
71−
72−~~~~~{.haskell}
73−postManipulation :: HakyllAction Context Context
74−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
75−~~~~~
76−
77−That manipulation will:
78−- Read the date from the file name the post was loaded from.
79−- Parse the date and render it in a `%B %e, %Y` format. This is a
80− `Month day, Year` format.
81−- Put the result in the `date` metadata field.
82−- If the date could not be parsed, it will put `"Unknown date"` in the `date`
53+- `$title`: Title of the item. This is set in our posts, since we use a `title`
8354 metadata field.
55+- `$url`: Url of the item. This is automatically set by Hakyll, so you shouldn't
56+ worry about it.
57+- `$description`: A description of our item to appear in the feed reader.
8458
85−So, we can throw away our `date: ` lines from our posts, and still use `$date`
86−in our templates.
87−
88−## Abstracting the post list
89−
90−Now, we're going to render tags. This is also done using context manipulations.
91−Hakyll has a specialized module to deal with tags, provided by
92−`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
93−the `tags` metadata field.
94−
95− ---
96− title: A third post
97− author: Publius Ovidius Naso
98− tags: epic fail, ovidius
99− ---
100− Pellentesque tempor blandit elit, vel...
101−
102−But first things first. We need to render a post list for every tag. We already
103−had some code to render a list of all posts. We're just going to abstract this
104−code into a more general function:
105−
106−~~~~{.haskell}
107−renderPostList url title posts = do
108− let list = createListingWith url ["templates/postitem.html"]
109− posts [("title", Left title)]
110− renderChain ["posts.html", "templates/default.html"] list
111−~~~~~
112−
113−Our "render all posts" action can now be written as:
114−
115−~~~~~{.haskell}
116−renderPostList "posts.html" "All posts" renderablePosts
117−~~~~~
118−
119−## Tag links
120−
121−We want to display the tags for our post under the title. But if we use the
122−`$tags` key in a template, we will just have the plain tags - they will not be
123−clickable. We can again solve this with a `ContextManipulation`. We have a
124−function that produces an url for a given tag:
125−
126−~~~~~{.haskell}
127−tagToUrl tag = "$root/tags/" ++ removeSpaces tag ++ ".html"
128−~~~~~
129−
130−`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
131−a specialized `renderValue` function for creating linked tags called
132−`renderTagLinks`. This function simply takes a function that produces an url
133−for a given tag - the function we just wrote. Let's extend our
134−`postManipulation`.
135−
136−~~~~~{.haskell}
137−postManipulation :: HakyllAction Context Context
138−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
139− >>> renderTagLinks tagToUrl
140−~~~~~
141−
142−We apply this manipulation when we load the tags.
143−
144−~~~~~{.haskell}
145−let renderablePosts =
146− map ((>>> postManipulation) . createPage) postPaths
147−~~~~~
148−
149−So, the `renderTagLinks` function replaces the `$tags` value from
150−`epic fail, random` to `<a href="$root/tags/epic-fail.html">epic fail</a>, ...`.
151−If we click a tag, we get a `404`. That's because we haven't generated the
152−post lists for every tag.
153−
154−## The Tag Map
155−
156−Hakyll provides a function called `readTagMap`. Let's inspect it's type.
157−
158−~~~~~{.haskell}
159−type TagMap = Map String [HakyllAction () Context]
160−readTagMap String [FilePath] -> HakyllAction () TagMap
161−~~~~~
162−
163−You give it a list of paths, and it creates a map that, for every tag, holds
164−a number of posts. We can easily use this to render a post list for every tag.
165−The first argument given is an "identifier", unique to this tag map. Hakyll
166−needs this so it can cache the tags.
59+The latter is obviously the problem: we don't have a description in our posts.
60+In fact, we would like to copy the `$body` key to the `$description` key, so
61+people can read the full post in their feed readers.
16762
168−~~~~~{.haskell}
169−let tagMap = readTagMap "postTags" postPaths
170−~~~~~
63+## Where arrows come in
17164
172−When we have the `TagMap`, we can need to render a post list for every tag.
173−There is a function in Hakyll designed more or less for this purpose:
174−`withTagMap`. This takes a `TagMap` and an action to execute for every tag and
175−it's associated posts. We pass a small function to it we create ourselves[^1]:
176−
177−[^1]: Exercise for the reader: why do we use `>>> postManipulation` again here?
65+The `Text.Hakyll.ContextManipulations` module contains a number of simple
66+functions that create Arrows for us. One of these functions is `copyValue`,
67+which takes a source and a destination key. So, we need to pass our
68+items through this Arrow first.
17869
17970~~~~~{.haskell}
180−let renderListForTag tag posts =
181− renderPostList (tagToUrl tag)
182− ("Posts tagged " ++ tag)
183− (map (>>> postManipulation) posts)
184−withTagMap tagMap renderPostList
71+renderRss myFeedConfiguration $
72+ map (>>> copyValue "body" "description") (take 3 postPages)
18573~~~~~
18674
187−There we go. We now have clickable tags, and a post list for every tag.
188−
189−## A Tag Cloud
190−
191−A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
192−generate a tag cloud. Let's have a look at the `renderTagCloud` function.
75+And that's that, now our feed gets rendered properly. Exercise for the reader
76+is to add a Atom feed[^2].
19377
194−~~~~~{.haskell}
195−renderTagCloud :: (String -> String)
196− -> Float
197− -> Float
198− -> HakyllAction TagMap String
199−~~~~~
200−
201−The first argument is, once again, a function to create an url for a given tag.
202−Then, we give a minimum and a maximum font size in percent, and we get a tag
203−cloud Arrow back. We can add this to our index:
204−
205−~~~~~{.haskell}
206−let tagCloud = tagMap >>> renderTagCloud tagToUrl 100 200
207− index = createListing "index.html"
208− ["templates/postitem.html"]
209− (take 3 renderablePosts)
210− [ ("title", Left "Home")
211− , ("tagcloud", Right tagCloud)
212− ]
213−renderChain ["index.html", "templates/default.html"] index
214−~~~~~
78+[^2]: Hint: look around in the [reference]($root/reference.html).
21579
21680## The gist of it
21781
218−- There's some handy, simple functions in `Text.Hakyll.ContextManipulations`.
219−- Seperate tags by commas and put them in the `$tags` field.
220−- Use `withTagMap` to render a list for every tag.
221−- Hakyll can also create tag clouds.
82+- Hakyll has native support for RSS and Atom feeds.
83+- The items must contain `$title` and `$description` fields.
84+- Arrows can be used to copy values in a `Context`.
examples/hakyll/tutorials/part07.markdown+183−64
@@ -1,102 +1,221 @@
11---
2−title: Interlude
3−what: gives some various tips and tricks about Hakyll (quite handy, read this!)
2+title: Tags and manipulations
3+what: enhances our blog with tags and explains context manipulations.
44---
55
6−## Syntax-highlighting
6+## Context manipulations
77
8−Pandoc (which Hakyll uses as a backend) offers powerful syntax highlighting.
9−To enable this, Pandoc needs to be compiled with highlighting support. If this
10−is not the case, you can fix this using:
8+Here, have [a zip file]($root/examples/tagblog.zip) for this tutorial.
119
10+You probably remember that `Context` objects are just key-value mappings. We can
11+render those with templates, and then the `$key`'s in the template get
12+substituted by the appropriate values. This is a rather flexible system, but
13+there are limitations. Some of these limitations can be solved using
14+_context manipulations_.
15+
16+Like rendering actions, _context manipulations_ are also simply
17+`HakyllAction Context Context` arrows. The `Text.Hakyll.ContextManipulations`
18+contains some functions to easily construct easy variants.
19+
20+One of the most general functions is the `renderValue` function. Let's have a
21+look at it's type.
22+
23+~~~~~{.haskell}
24+renderValue :: String
25+ -> String
26+ -> (String -> String)
27+ -> HakyllAction Context Context
28+~~~~~
29+
30+This is the preferred way of creating context manipulations. The first argument
31+is the `key` to manipulate. The second argument is the `key` where the new value
32+should be placed. If this is the same as the first argument, it will be
33+replaced. The third argument is the function to manipulate the `value` with.
34+
35+As a simple example, let's write a function that puts the `$title` in uppercase.
36+
37+~~~~~{.haskell}
38+import Data.Char (toUpper)
39+
40+titleUpper :: HakyllAction Context Context
41+titleUpper = renderValue "title" "title" $ map toUpper
1242~~~~~
13−[jasper@alice ~]$ cabal install --reinstall -fhighlighting pandoc
43+
44+Because the destination `key` is the same as the source `key`, we can also use
45+the `changeValue` function here.
46+
47+~~~~~{.haskell}
48+titleUpper = changeValue "title" $ map toUpper
1449~~~~~
1550
16−## Auto-compilation
51+For further reading, refer to the `Text.Hakyll.ContextManipulations`
52+documentation.
1753
18−Hakyll features a simple _auto-compilation_ mode. This is invoked by running
54+## Applying Context Manipulations
1955
56+Because we're dealing with Arrows again, we can use `>>>` to apply our
57+manipulations. For example, we could use or title manipulation like this:
58+
59+~~~~~{.haskell}
60+renderChain ["templates/default.html"]
61+ (createPage "index.markdown" >>> titleUpper)
2062~~~~~
21−[jasper@alice ~]$ ./hakyll preview
22−Starting hakyll server on port 8000...
63+
64+## Rendering dates
65+
66+As you remember, in our previous blog, all posts had a file name like
67+`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
68+had a metadata field `date`, containing a human-readable date. This is not very
69+D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
70+dates encoded in paths: `renderDate`.
71+
72+~~~~~{.haskell}
73+postManipulation :: HakyllAction Context Context
74+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
2375~~~~~
2476
25−Now, Hakyll will recompile your site when you change files, so you can just
26−refresh in your browser. There is one more thing to note: this will not update
27−your site automatically when `hakyll.hs` changes. So if you make any changes to
28−the configuration file, you'll have to compile it again, and then you can enter
29−`preview` mode again.
77+That manipulation will:
78+- Read the date from the file name the post was loaded from.
79+- Parse the date and render it in a `%B %e, %Y` format. This is a
80+ `Month day, Year` format.
81+- Put the result in the `date` metadata field.
82+- If the date could not be parsed, it will put `"Unknown date"` in the `date`
83+ metadata field.
84+
85+So, we can throw away our `date: ` lines from our posts, and still use `$date`
86+in our templates.
87+
88+## Abstracting the post list
89+
90+Now, we're going to render tags. This is also done using context manipulations.
91+Hakyll has a specialized module to deal with tags, provided by
92+`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
93+the `tags` metadata field.
94+
95+ ---
96+ title: A third post
97+ author: Publius Ovidius Naso
98+ tags: epic fail, ovidius
99+ ---
100+ Pellentesque tempor blandit elit, vel...
101+
102+But first things first. We need to render a post list for every tag. We already
103+had some code to render a list of all posts. We're just going to abstract this
104+code into a more general function:
105+
106+~~~~{.haskell}
107+renderPostList url title posts = do
108+ let list = createListingWith url ["templates/postitem.html"]
109+ posts [("title", Left title)]
110+ renderChain ["posts.html", "templates/default.html"] list
111+~~~~~
30112
31−## When to rebuild
113+Our "render all posts" action can now be written as:
32114
33−If you execute a `./hakyll build`, Hakyll will build your site incrementally.
34−This means it will be very fast, but it will not pick up _all_ changes.
115+~~~~~{.haskell}
116+renderPostList "posts.html" "All posts" renderablePosts
117+~~~~~
35118
36−- In case you edited `hakyll.hs`, you first want to compile it again.
37−- It is generally recommended to do a `./hakyll rebuild` before you deploy your
38− site.
119+## Tag links
39120
40−## Pretty URL's
121+We want to display the tags for our post under the title. But if we use the
122+`$tags` key in a template, we will just have the plain tags - they will not be
123+clickable. We can again solve this with a `ContextManipulation`. We have a
124+function that produces an url for a given tag:
41125
42−There is an option in Hakyll to produce pretty URL's, which is disabled by
43−default because it can be confusing when you're first introduced to Hakyll.
126+~~~~~{.haskell}
127+tagToUrl tag = "$root/tags/" ++ removeSpaces tag ++ ".html"
128+~~~~~
44129
45−It can be enabled this way:
130+`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
131+a specialized `renderValue` function for creating linked tags called
132+`renderTagLinks`. This function simply takes a function that produces an url
133+for a given tag - the function we just wrote. Let's extend our
134+`postManipulation`.
46135
47136~~~~~{.haskell}
48−import Text.Hakyll
49−import Text.Hakyll.Hakyll
137+postManipulation :: HakyllAction Context Context
138+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
139+ >>> renderTagLinks tagToUrl
140+~~~~~
50141
51−myConfig :: HakyllConfiguration
52−myConfig = (defaultHakyllConfiguration "http://jaspervdj.be")
53− { enableIndexUrl = True
54− }
142+We apply this manipulation when we load the tags.
55143
56−main = hakyllWithConfiguration myConfig $ do
57− -- Further code here
144+~~~~~{.haskell}
145+let renderablePosts =
146+ map ((>>> postManipulation) . createPage) postPaths
58147~~~~~
59148
60−The effect will be that the internal `toUrl` function will behave differently.
61−A few examples:
149+So, the `renderTagLinks` function replaces the `$tags` value from
150+`epic fail, random` to `<a href="$root/tags/epic-fail.html">epic fail</a>, ...`.
151+If we click a tag, we get a `404`. That's because we haven't generated the
152+post lists for every tag.
62153
63−- `about.html` will be rendered to `about/index.html`.
64−- `posts/2010-02-16-a-post.markdown` will be rendered to
65− `posts/2010-02-16-a-post/index.html`.
66−- However, `index.markdown` will still be rendered to `index.html`. Likewise,
67− `posts/index.html` would be rendered to `posts.index.html`.
154+## The Tag Map
68155
69−The benefit of this is simply prettier URL's. That is, if you consider
70−`example.com/about` prettier than `example.com/about.html`.
156+Hakyll provides a function called `readTagMap`. Let's inspect it's type.
71157
72−## Default values
158+~~~~~{.haskell}
159+type TagMap = Map String [HakyllAction () Context]
160+readTagMap String [FilePath] -> HakyllAction () TagMap
161+~~~~~
73162
74−At some point, you might want to use a number of global key-value pairs, for
75−example, `$author`. There are two possible ways to achieve this.
163+You give it a list of paths, and it creates a map that, for every tag, holds
164+a number of posts. We can easily use this to render a post list for every tag.
165+The first argument given is an "identifier", unique to this tag map. Hakyll
166+needs this so it can cache the tags.
76167
77−- There is an option in `HakyllConfiguration` supporting this, called
78− `additionalContext`. For an example on how to use `HakyllConfiguration`, see
79− the pretty URL's section above.
168+~~~~~{.haskell}
169+let tagMap = readTagMap "postTags" postPaths
170+~~~~~
80171
81−- Another option is to use a `defaults.markdown` file, simply containing some
82− metadata, and then `combine` this file with other pages. The advantage is
83− that autocompilation mode will pick up changes in this file[^1].
172+When we have the `TagMap`, we can need to render a post list for every tag.
173+There is a function in Hakyll designed more or less for this purpose:
174+`withTagMap`. This takes a `TagMap` and an action to execute for every tag and
175+it's associated posts. We pass a small function to it we create ourselves[^1]:
84176
85−[^1]: Original idea by zenzike.
177+[^1]: Exercise for the reader: why do we use `>>> postManipulation` again here?
86178
87−## Markup in templates
179+~~~~~{.haskell}
180+let renderListForTag tag posts =
181+ renderPostList (tagToUrl tag)
182+ ("Posts tagged " ++ tag)
183+ (map (>>> postManipulation) posts)
184+withTagMap tagMap renderPostList
185+~~~~~
186+
187+There we go. We now have clickable tags, and a post list for every tag.
88188
89−Most of the examples in these tutorials use HTML for templates. However, since
90−Hakyll 2.2, it is possible use other markup languages in your templates. Simply
91−use an appropriate extension, and Hakyll will pick it up. For example, you could
92−write your `templates/post.markdown` template as:
189+## A Tag Cloud
93190
94− # $title
191+A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
192+generate a tag cloud. Let's have a look at the `renderTagCloud` function.
95193
96− _On $date_
194+~~~~~{.haskell}
195+renderTagCloud :: (String -> String)
196+ -> Float
197+ -> Float
198+ -> HakyllAction TagMap String
199+~~~~~
200+
201+The first argument is, once again, a function to create an url for a given tag.
202+Then, we give a minimum and a maximum font size in percent, and we get a tag
203+cloud Arrow back. We can add this to our index:
204+
205+~~~~~{.haskell}
206+let tagCloud = tagMap >>> renderTagCloud tagToUrl 100 200
207+ index = createListing "index.html"
208+ ["templates/postitem.html"]
209+ (take 3 renderablePosts)
210+ [ ("title", Left "Home")
211+ , ("tagcloud", Right tagCloud)
212+ ]
213+renderChain ["index.html", "templates/default.html"] index
214+~~~~~
97215
98− $body
216+## The gist of it
99217
100−__Warning__: you shouldn't use markdown for your "root" template, as these
101−templates will never insert things like the doctype for you -- so you always
102−need at least one top-level HTML template.
218+- There's some handy, simple functions in `Text.Hakyll.ContextManipulations`.
219+- Seperate tags by commas and put them in the `$tags` field.
220+- Use `withTagMap` to render a list for every tag.
221+- Hakyll can also create tag clouds.
examples/hakyll/tutorials/part08.markdown+58−77
@@ -1,104 +1,85 @@
11---
2−title: CategoryBlog
3−what: explains how to use categories instead of tags
2+title: Interlude
3+what: gives some various tips and tricks about Hakyll (quite handy, read this!)
44---
55
6−## Categories
6+## Syntax-highlighting
77
8−Most people familiar with "tags" will also know the concept "categories".
8+Pandoc (which Hakyll uses as a backend) offers powerful syntax highlighting.
9+To enable this, Pandoc needs to be compiled with highlighting support. If this
10+is not the case, you can fix this using:
911
10−![Tags illustration]($root/images/tutorial8-tags.png)
11−
12−In fact, tags are harder to implement because they have to be represented as a
13−many-to-many relation, and categories are a simple 1-to-many relation.
12+~~~~~
13+[jasper@alice ~]$ cabal install --reinstall -fhighlighting pandoc
14+~~~~~
1415
15−![Tags illustration]($root/images/tutorial8-categories.png)
16+## Auto-compilation
1617
17−This is also the reason you can "simulate" categories using tags. In this
18−tutorial we will adapt the blog to use categories instead of tags. Here is
19−[a zip file]($root/examples/categoryblog.zip) containing the files used in this
20−tutorial.
18+Hakyll features a simple _auto-compilation_ mode. This is invoked by running
2119
22−## About category support
20+~~~~~
21+[jasper@alice ~]$ ./hakyll preview
22+Starting hakyll server on port 8000...
23+~~~~~
2324
24−Categories are simpler, but they are usually used in custom ways. That's why
25−Hakyll provides less "standard" functions to deal with them. But this gives us
26−another chance to learn some of the things we can do with Hakyll.
25+Now, Hakyll will recompile your site when you change files, so you can just
26+refresh in your browser. There is one more thing to note: this will not update
27+your site automatically when `hakyll.hs` changes. So if you make any changes to
28+the configuration file, you'll have to compile it again, and then you can enter
29+`preview` mode again.
2730
28−## Reading Categories
31+## When to rebuild
2932
30−Tags are located in the `tags` metadata field. Since one post can only belong
31−in one category, a different approach was chosen here. The category of a post
32−is determined by the subfolder it is in. Here you see the directory layout for
33−our posts using categories:
33+If you execute a `./hakyll build`, Hakyll will build your site incrementally.
34+This means it will be very fast, but it will not pick up _all_ changes.
3435
35− posts
36− |-- coding
37− | |-- 2009-11-05-a-first-post.markdown
38− | |-- 2009-11-28-a-third-post.markdown
39− | `-- 2009-12-04-this-blog-aint-dead.markdown
40− `-- random
41− |-- 2009-11-10-another-post.markdown
42− `-- 2009-12-23-almost-christmas.markdown
36+- In case you edited `hakyll.hs`, you first want to compile it again.
37+- It is generally recommended to do a `./hakyll rebuild` before you deploy your
38+ site.
4339
44−Because we find all our posts in different subdirectories, sorting them is a
45−little harder: we still want them sorted by date, so it boils down to sorting
46−them by "base name". I hope it does not surprise you Hakyll provides a function
47−for that:
40+## Pretty URL's
4841
49−~~~~~{.haskell}
50−postPaths <- liftM (reverse . sortByBaseName)
51− (getRecursiveContents "posts")
52−~~~~~
42+There is an option in Hakyll to produce pretty URL's, which is disabled by
43+default because it can be confusing when you're first introduced to Hakyll.
5344
54−We reverse them again, because we want the most recent posts first. Now, we can
55−use the `readCategoryMap` function instead of `readTagMap`, which has the same
56−signature, but assigns categories based on the folders the posts are in.
45+It can be enabled this way:
5746
5847~~~~~{.haskell}
59−categoryMap <- readCategoryMap "categoryMap" renderablePosts
60−~~~~~
61−
62−The rest of the `hakyll.hs` is very similar to the one in the previous
63−tutorial, except we want to render a category list instead of a tag cloud.
48+import Text.Hakyll
49+import Text.Hakyll.Hakyll
6450
65−## Rendering a category list
51+myConfig :: HakyllConfiguration
52+myConfig = (defaultHakyllConfiguration "http://jaspervdj.be")
53+ { enableIndexUrl = True
54+ }
6655
67−Because rendering a category list is quite easy, and it would be hard to
68−write a "general" function for this, hakyll does not provide such a function --
69−but it is not hard to write. First, we write an auxiliary function that produces
70−a list item for one category:
71−
72−~~~~~{.haskell}
73−categoryListItem category posts =
74− "<li>" ++ link category (categoryToUrl category)
75− ++ " - " ++ show (length posts) ++ " items.</li>"
56+main = hakyllWithConfiguration myConfig $ do
57+ -- Further code here
7658~~~~~
7759
78−This is nothing more that some basic string concatenation to create a `li` HTML
79−element. The function that applies this on every element in the `TagMap` is more
80−interesting:
60+The effect will be that the internal `toUrl` function will behave differently.
61+A few examples:
8162
82−~~~~~{.haskell}
83−categoryList :: HakyllAction TagMap String
84−categoryList = arr $ uncurry categoryListItem <=< toList
85−~~~~~
63+- `about.html` will be rendered to `about/index.html`.
64+- `posts/2010-02-16-a-post.markdown` will be rendered to
65+ `posts/2010-02-16-a-post/index.html`.
66+- However, `index.markdown` will still be rendered to `index.html`. Likewise,
67+ `posts/index.html` would be rendered to `posts.index.html`.
68+
69+The benefit of this is simply prettier URL's. That is, if you consider
70+`example.com/about` prettier than `example.com/about.html`.
8671
87−This function might seem a little harder to understand if you are not familiar
88−with the `<=<` operator -- but it's just right-to-left monad composition in the
89−list monad. `uncurry categoryListItem <=< toList` is a pure function we want to
90−execute on the `TagMap`. But this is not possible in Hakyll[^1]. We need to make
91−an arrow of this function. The `arr` function solves this problem easily.
72+## Default values
9273
93−[^1]: This is a feature, not a bug. It helps dependency handling.
74+At some point, you might want to use a number of global key-value pairs, for
75+example, `$author`. There are two possible ways to achieve this.
9476
95−We then add this to our index page, and we are done. Feel free to hack around
96−with the source code. If you still have questions, feel free to ask them at the
97−[google discussion group](http://groups.google.com/group/hakyll).
77+- There is an option in `HakyllConfiguration` supporting this, called
78+ `additionalContext`. For an example on how to use `HakyllConfiguration`, see
79+ the pretty URL's section above.
9880
99−## The gist of it
81+- Another option is to use a `defaults.markdown` file, simply containing some
82+ metadata, and then `combine` this file with other pages. The advantage is
83+ that autocompilation mode will pick up changes in this file[^1].
10084
101−- Hakyll supports categories as well as tags.
102−- Tags are actually a generalization of categories.
103−- Use `readCategoryMap` to read categories.
104−- You need to write some custom functions to render category lists etc.
85+[^1]: Original idea by zenzike.
examples/hakyll/tutorials/part09.markdownnew file+104
@@ -0,0 +1,104 @@
1+---
2+title: CategoryBlog
3+what: explains how to use categories instead of tags
4+---
5+
6+## Categories
7+
8+Most people familiar with "tags" will also know the concept "categories".
9+
10+![Tags illustration]($root/images/tutorial8-tags.png)
11+
12+In fact, tags are harder to implement because they have to be represented as a
13+many-to-many relation, and categories are a simple 1-to-many relation.
14+
15+![Tags illustration]($root/images/tutorial8-categories.png)
16+
17+This is also the reason you can "simulate" categories using tags. In this
18+tutorial we will adapt the blog to use categories instead of tags. Here is
19+[a zip file]($root/examples/categoryblog.zip) containing the files used in this
20+tutorial.
21+
22+## About category support
23+
24+Categories are simpler, but they are usually used in custom ways. That's why
25+Hakyll provides less "standard" functions to deal with them. But this gives us
26+another chance to learn some of the things we can do with Hakyll.
27+
28+## Reading Categories
29+
30+Tags are located in the `tags` metadata field. Since one post can only belong
31+in one category, a different approach was chosen here. The category of a post
32+is determined by the subfolder it is in. Here you see the directory layout for
33+our posts using categories:
34+
35+ posts
36+ |-- coding
37+ | |-- 2009-11-05-a-first-post.markdown
38+ | |-- 2009-11-28-a-third-post.markdown
39+ | `-- 2009-12-04-this-blog-aint-dead.markdown
40+ `-- random
41+ |-- 2009-11-10-another-post.markdown
42+ `-- 2009-12-23-almost-christmas.markdown
43+
44+Because we find all our posts in different subdirectories, sorting them is a
45+little harder: we still want them sorted by date, so it boils down to sorting
46+them by "base name". I hope it does not surprise you Hakyll provides a function
47+for that:
48+
49+~~~~~{.haskell}
50+postPaths <- liftM (reverse . sortByBaseName)
51+ (getRecursiveContents "posts")
52+~~~~~
53+
54+We reverse them again, because we want the most recent posts first. Now, we can
55+use the `readCategoryMap` function instead of `readTagMap`, which has the same
56+signature, but assigns categories based on the folders the posts are in.
57+
58+~~~~~{.haskell}
59+categoryMap <- readCategoryMap "categoryMap" renderablePosts
60+~~~~~
61+
62+The rest of the `hakyll.hs` is very similar to the one in the previous
63+tutorial, except we want to render a category list instead of a tag cloud.
64+
65+## Rendering a category list
66+
67+Because rendering a category list is quite easy, and it would be hard to
68+write a "general" function for this, hakyll does not provide such a function --
69+but it is not hard to write. First, we write an auxiliary function that produces
70+a list item for one category:
71+
72+~~~~~{.haskell}
73+categoryListItem category posts =
74+ "<li>" ++ link category (categoryToUrl category)
75+ ++ " - " ++ show (length posts) ++ " items.</li>"
76+~~~~~
77+
78+This is nothing more that some basic string concatenation to create a `li` HTML
79+element. The function that applies this on every element in the `TagMap` is more
80+interesting:
81+
82+~~~~~{.haskell}
83+categoryList :: HakyllAction TagMap String
84+categoryList = arr $ uncurry categoryListItem <=< toList
85+~~~~~
86+
87+This function might seem a little harder to understand if you are not familiar
88+with the `<=<` operator -- but it's just right-to-left monad composition in the
89+list monad. `uncurry categoryListItem <=< toList` is a pure function we want to
90+execute on the `TagMap`. But this is not possible in Hakyll[^1]. We need to make
91+an arrow of this function. The `arr` function solves this problem easily.
92+
93+[^1]: This is a feature, not a bug. It helps dependency handling.
94+
95+We then add this to our index page, and we are done. Feel free to hack around
96+with the source code. If you still have questions, feel free to ask them at the
97+[google discussion group](http://groups.google.com/group/hakyll).
98+
99+## The gist of it
100+
101+- Hakyll supports categories as well as tags.
102+- Tags are actually a generalization of categories.
103+- Use `readCategoryMap` to read categories.
104+- You need to write some custom functions to render category lists etc.