@@ -1,211 +1,124 @@
11---
22title: Tutorial (Part IV)
3−what: adds tags and context manipulations to our blog
3+what: adds an RSS feed to the blog from the previous tutorial
44---
55
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− -> (String -> String)
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 Data.Char (toUpper)
41−
42−titleUpper :: ContextManipulation
43−titleUpper = renderValue "title" "title" $ map toUpper
44−~~~~~
45−
46−## Applying Context Manipulations
47−
48−Now, the question is how to apply these `ContextManipulation`'s. The answer is
49−simple. For every important render function (`render`, `renderChain`,
50−`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
51−first argument. These functions are thus: `renderWith`, `renderChainWith`,
52−`renderAndConcatWith`. In fact, the following holds true:
53−
54−~~~~~{.haskell}
55−render == renderWith id
56−renderChain == renderChainWith id
57−renderAndConcat == renderAndConcatWith id
6+## Adding RSS to our simple blog
7+
8+In this tutorial, we're going to add an RSS feed to the blog we wrote in
9+[the previous tutorial](tutorial2.html). Here is a
10+[zip file containing the source](examples/rssblog.zip).
11+
12+An RSS feed looks like this:
13+
14+~~~~~{.xml}
15+<?xml version="1.0" ?>
16+<rss version="2.0">
17+ <channel>
18+ <title>The SimpleBlog</title>
19+ <link>http://example.com/</link>
20+ <description>Simple blog in hakyll</description>
21+ <item>
22+ <title>Title goes here</title>
23+ <link>http://example.com/post.html</link>
24+ <description>
25+ A description is optional.
26+ </description>
27+ </item>
28+ </channel>
29+</rss>
5830~~~~~
5931
60−So we could use or title manipulation like this:
61−
62−~~~~~{.haskell}
63−renderChainWith titleUpper ["templates/default.html"]
64− (createPagePath "index.markdown")
32+Note that, obviously, there can be more than one item. We're going to use a
33+template to render this. This is where `templates/rss.xml` comes in:
34+
35+~~~~~{.xml}
36+<?xml version="1.0" ?>
37+<rss version="2.0">
38+ <channel>
39+ <title>The SimpleBlog</title>
40+ <link>http://jaspervdj.be/</link>
41+ <description>Simple blog in hakyll</description>
42+ $items
43+ </channel>
44+</rss>
6545~~~~~
6646
67−## Rendering dates
68−
69−As you remember, in our previous blog, all posts had a file name like
70−`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
71−had a metadata field `date`, containing a human-readable date. This is not very
72−D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
73−dates encoded in paths: `renderDate`.
47+We thus render our feed with the following code (no, I didn't define `rssPage`
48+yet - I'm going to work from bottom to top here, it's easier to explain it
49+that way).
7450
7551~~~~~{.haskell}
76−postManipulation :: ContextManipulation
77−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
78−~~~~~
79−
80−That manipulation will:
81−- Read the date from the file name the post was loaded from.
82−- Parse the date and render it in a `%B %e, %Y` format. This is a
83− `Month day, Year` format.
84−- Put the result in the `date` metadata field.
85−- If the date could not be parsed, it will put `"Unknown date"` in the `date`
86− metadata field.
87−
88−So, we can throw away our `date: ` lines from our posts, and still use `$date`
89−in our templates.
90−
91−## Abstracting the post list
92−
93−Now, we're going to render tags. This is also done using context manipulations.
94−Hakyll has a specialized module to deal with tags, provided by
95−`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
96−the `tags` metadata field.
97−
98−> ---
99−> title: A third post
100−> author: Publius Ovidius Naso
101−> tags: epic fail, ovidius
102−> ---
103−> Pellentesque tempor blandit elit, vel...
104−
105−But first things first. We need to render a post list for every tag. We already
106−had some code to render a list of all posts. We're just going to abstract this
107−code into a more general function:
108−
109−~~~~{.haskell}
110−renderPostList url title posts = do
111− let postItems = renderAndConcatWith
112− postManipulation
113− "templates/postitem.html"
114− (map createPagePath posts)
115− customPage = createCustomPage
116− url
117− ("templates/postitem.html" : posts)
118− [ ("title", Left title)
119− , ("posts", Right postItems)
120− ]
121− renderChain ["posts.html", "templates/default.html"]
122− customPage
52+renderChain ["templates/rss.xml"] rssPage
12353~~~~~
12454
125−Our "render all posts" action can now be written as:
55+This, as you can see, is a regular render chain, once again. We need make a
56+`Renderable` that "fills in" the `$items` identifier. We're going to do this
57+using a [custom page](tutorial2.html#custom-pages).
12658
127−~~~~~{.haskell}
128−renderPostList "posts.html" "All posts" postPaths
129−~~~~~
59+## Custom pages again
13060
131−## Tag links
61+Note that we do not have to include all posts in the rss feed - only a few
62+recent ones. We'll settle on the latest three here.
13263
133−We want to display the tags for our post under the title. But if we use the
134−`$tags` key in a template, we will just have the plain tags - they will not be
135−clickable. We can again solve this with a `ContextManipulation`. We have a
136−function that produces an url for a given tag:
64+We want to render every post using the following template,
65+`templates/rssitem.xml`:
13766
13867~~~~~{.haskell}
139−tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
68+<item>
69+ <title>$title</title>
70+ <link>http://example.com/$url</link>
71+ <description>$title by $author</description>
72+</item>
14073~~~~~
14174
142−`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
143−a specialized `renderValue` function for creating linked tags called
144−`renderTagLinks`. This function simply takes a function that produces an url
145−for a given tag - the function we just wrote. Let's extend our
146−`postManipulation`.
75+Since we build on the previous example, we still have our `renderablePosts`
76+list. We'll be using it again:
14777
14878~~~~~{.haskell}
149−postManipulation :: ContextManipulation
150−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
151− . renderTagLinks tagToURL
79+let recentRSSItems = renderAndConcat ["templates/rssitem.xml"]
80+ (take 3 renderablePosts)
15281~~~~~
15382
154−If we click a tag, we get a `404`. That's because we haven't generated the
155−post lists for every tag.
83+We're using the `renderAndConcat` function again. Note that because of
84+hakyll/haskell laziness, this action isn't executed directly, and this helps
85+dependency handling.
15686
157−## The Tag Map
158−
159−Hakyll provides a function called `readTagMap`. Let's inspect it's type.
87+Now, the `rssPage` page. As you might remember, we use the `createCustomPage`
88+function to create a custom page. We first give the destination url, then a
89+list of dependencies, and then a list of `(key, value)` pairs.
16090
16191~~~~~{.haskell}
162−readTagMap [FilePath] -> IO Map String [FilePath]
92+let rssPage = createCustomPage
93+ "rss.xml"
94+ ("templates/postitem.html" : take 3 postPaths)
95+ [("items", Right recentRSSItems)]
16396~~~~~
16497
165−You give it a list of paths, and it creates a map that, for every tag, holds
166−a number of posts. We can easily use this to render a post list for every tag.
98+## Adding a link to the feed
16799
168−~~~~~{.haskell}
169−tagMap <- readTagMap postPaths
170−let renderListForTag (tag, posts) =
171− renderPostList (tagToURL tag)
172− ("Posts tagged " ++ tag)
173−mapM_ renderListForTag (toList tagMap)
174−~~~~~
175−
176−There we go. We now have clickable tags, and a post list for every tag.
177−
178−## A Tag Cloud
100+According to the w3 organization,
101+[you should add \<link\> tags](http://www.w3.org/QA/Tips/use-links) to your
102+documents, so we'll do this. In `templates/default.html`:
179103
180−A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
181−generate a tag cloud. Let's have a look at the `renderTagCloud` function.
104+~~~~~~{.html}
105+<head>
106+ <title>SimpleBlog - $title</title>
107+ <link rel="stylesheet" type="text/css" href="$$root/css/default.css" />
108+ <link rel="alternate"
109+ type="application/rss+xml"
110+ title="SimpleBlog"
111+ href="http://example.com/rss.xml" />
112+</head>
113+~~~~~~
182114
183−~~~~~{.haskell}
184−TagCloud :: M.Map String [FilePath]
185− -> (String -> String)
186− -> Float
187− -> Float
188− -> String
189−~~~~~
190−
191−The first argument is obviously the result of the `readTagMap` function. The
192−second argument is, once again, a function to create an url for a given tag.
193−Then, we give a minimum and a maximum font size in percent, and we get the
194−tag cloud back. We can add this to our index:
195−
196−~~~~~{.haskell}
197−let tagCloud = renderTagCloud tagMap tagToURL 100 200
198−...
199−createCustomPage "index.html"
200− ("templates/postitem.html" : take 3 postPaths)
201− [ ("title", Left "Home")
202− , ("posts", Right recentPosts)
203− , ("tagcloud", Left tagCloud)
204− ]
205−~~~~~
115+This makes most browsers see the rss feed, and show it in the address bar.
116+If you want, you can also add a pretty button to your blog linking to
117+`rss.xml`.
206118
207−## That's it
119+## That's it!
208120
209−Feel free to hack around with the code from the zip file. As always, if you
210−still have questions, ask them at the
121+Yep, that's it. Feel free to play with the source code in
122+[the zip file](examples/rssblog.zip) and extend the blog further. As always,
123+all questions are welcome on the
211124[google discussion group](http://groups.google.com/group/hakyll).