@@ -0,0 +1,214 @@
1+---
2+title: Tutorial (Part IV)
3+what: adds tags and context manipulations to our blog
4+---
5+
6+Here, have [a zip file](examples/tagblog.zip) for this tutorial.
7+
8+## Context manipulations
9+
10+As you might remember, `Renderable` objects are usually just key-value mappings.
11+We can 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+In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
17+
18+~~~~~{.haskell}
19+type ContextManipulation = Context -> Context
20+~~~~~
21+
22+Where `Context` is a key-value mapping. Usually, you don't create
23+`ContextManipulation`'s by hand, but you use the `renderValue` function. Let's
24+have a look at it's type.
25+
26+~~~~~{.haskell}
27+renderValue :: String -> String
28+ -> (ByteString -> ByteString)
29+ -> ContextManipulation
30+~~~~~
31+
32+This is the preferred way of creating context manipulations. The first argument
33+is the `key` to manipulate. The second argument is the `key` where the new value
34+should be placed. If this is the same as the first argument, it will be
35+replaced. The third argument is the function to manipulate the `value` with.
36+
37+As a simple example, let's write a function that puts the `$title` in uppercase.
38+
39+~~~~~{.haskell}
40+import qualified Data.ByteString.Lazy.Char8 as B
41+import Data.Char (toUpper)
42+
43+titleUpper :: ContextManipulation
44+titleUpper = renderValue "title" "title" $ B.map toUpper
45+~~~~~
46+
47+## Applying Context Manipulations
48+
49+Now, the question is how to apply these `ContextManipulation`'s. The answer is
50+simple. For every important render function (`render`, `renderChain`,
51+`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
52+first argument. These functions are thus: `renderWith`, `renderChainWith`,
53+`renderAndConcatWith`. In fact, the following holds true:
54+
55+~~~~~{.haskell}
56+render == renderWith id
57+renderChain == renderChainWith id
58+renderAndConcat == renderAndConcatWith id
59+~~~~~
60+
61+So we could use or title manipulation like this:
62+
63+~~~~~{.haskell}
64+renderChainWith titleUpper ["templates/default.html"]
65+ (createPagePath "index.markdown")
66+~~~~~
67+
68+## Rendering dates
69+
70+As you remember, in our previous blog, all posts had a file name like
71+`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
72+had a metadata field `date`, containing a human-readable date. This is not very
73+D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
74+dates encoded in paths: `renderDate`.
75+
76+~~~~~{.haskell}
77+postManipulation :: ContextManipulation
78+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
79+~~~~~
80+
81+That manipulation will:
82+- Read the date from the file name the post was loaded from.
83+- Parse the date and render it in a `%B %e, %Y` format. This is a
84+ `Month day, Year` format.
85+- Put the result in the `date` metadata field.
86+- If the date could not be parsed, it will put `"Unknown date"` in the `date`
87+ metadata field.
88+
89+So, we can throw away our `date: ` lines from our posts, and still use `$date`
90+in our templates.
91+
92+## Abstracting the post list
93+
94+Now, we're going to render tags. This is also done using context manipulations.
95+Hakyll has a specialized module to deal with tags, provided by
96+`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
97+the `tags` metadata field.
98+
99+~~~~~
100+---
101+title: A third post
102+author: Publius Ovidius Naso
103+tags: epic fail, ovidius
104+---
105+Pellentesque tempor blandit elit, vel...
106+~~~~~
107+
108+But first things first. We need to render a post list for every tag. We already
109+had some code to render a list of all posts. We're just going to abstract this
110+code into a more general function:
111+
112+~~~~{.haskell}
113+renderPostList url title posts = do
114+ let postItems = renderAndConcatWith
115+ postManipulation
116+ "templates/postitem.html"
117+ (map createPagePath posts)
118+ customPage = createCustomPage
119+ url
120+ ("templates/postitem.html" : posts)
121+ [ ("title", Left title)
122+ , ("posts", Right postItems)
123+ ]
124+ renderChain ["posts.html", "templates/default.html"]
125+ customPage
126+~~~~~
127+
128+Our "render all posts" action can now be written as:
129+
130+~~~~~{.haskell}
131+renderPostList "posts.html" "All posts" postPaths
132+~~~~~
133+
134+## Tag links
135+
136+We want to display the tags for our post under the title. But if we use the
137+`$tags` key in a template, we will just have the plain tags - they will not be
138+clickable. We can again solve this with a `ContextManipulation`. We have a
139+function that produces an url for a given tag:
140+
141+~~~~~{.haskell}
142+tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
143+~~~~~
144+
145+`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
146+a specialized `renderValue` function for creating linked tags called
147+`renderTagLinks`. This function simply takes a function that produces an url
148+for a given tag - the function we just wrote. Let's extend our
149+`postManipulation`.
150+
151+~~~~~{.haskell}
152+postManipulation :: ContextManipulation
153+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
154+ . renderTagLinks tagToURL
155+~~~~~
156+
157+If we click a tag, we get a `404`. That's because we haven't generated the
158+post lists for every tag.
159+
160+## The Tag Map
161+
162+Hakyll provides a function called `readTagMap`. Let's inspect it's type.
163+
164+~~~~~{.haskell}
165+readTagMap [FilePath] -> IO Map String [FilePath]
166+~~~~~
167+
168+You give it a list of paths, and it creates a map that, for every tag, holds
169+a number of posts. We can easily use this to render a post list for every tag.
170+
171+~~~~~{.haskell}
172+tagMap <- readTagMap postPaths
173+let renderListForTag (tag, posts) =
174+ renderPostList (tagToURL tag)
175+ ("Posts tagged " ++ tag)
176+mapM_ renderListForTag (toList tagMap)
177+~~~~~
178+
179+There we go. We now have clickable tags, and a post list for every tag.
180+
181+## A Tag Cloud
182+
183+A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
184+generate a tag cloud. Let's have a look at the `renderTagCloud` function.
185+
186+~~~~~{.haskell}
187+TagCloud :: M.Map String [FilePath]
188+ -> (String -> String)
189+ -> Float
190+ -> Float
191+ -> String
192+~~~~~
193+
194+The first argument is obviously the result of the `readTagMap` function. The
195+second argument is, once again, a function to create an url for a given tag.
196+Then, we give a minimum and a maximum font size in percent, and we get the
197+tag cloud back. We can add this to our index:
198+
199+~~~~~{.haskell}
200+let tagCloud = renderTagCloud tagMap tagToURL 100 200
201+...
202+createCustomPage "index.html"
203+ ("templates/postitem.html" : take 3 postPaths)
204+ [ ("title", Left "Home")
205+ , ("posts", Right recentPosts)
206+ , ("tagcloud", Left tagCloud)
207+ ]
208+~~~~~
209+
210+## That's it
211+
212+Feel free to hack around with the code from the zip file. As always, if you
213+still have questions, ask them at the
214+[google discussion group](http://groups.google.com/group/hakyll).