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