on master help sign in
Wrote another tutorial, and updated some tutorials.
commit0ba0eac8d96c11d13163c7ec184392513de7ca77 authorJasper Van der Jeugt <jaspervdj@gmail.com> dateTue 19 Jan 2010, 17:17 (17 years ago) parentdb0eef8
12 files changed+699−388
examples/hakyll/tutorial1.markdown+7−7 examples/hakyll/tutorial2.markdown+109−112 examples/hakyll/tutorial3.markdown+130−92 examples/hakyll/tutorial4.markdown+90−177 examples/hakyll/tutorial5.markdown+211 examples/morepages/about.markdown+27 examples/morepages/css/default.css+27 examples/morepages/footer.markdown+16 examples/morepages/hakyll.hs+13 examples/morepages/index.markdown+24 examples/morepages/products.markdown+23 examples/morepages/templates/default.html+22
examples/hakyll/tutorial1.markdown+7−7
@@ -66,12 +66,12 @@ metadata. The metadata is placed in the file header and surrouded by `---`
6666lines. Each line should contain a `key: value` pair. Let's have a look at the
6767`about.markdown` page.
6868
69−> ---
70−> title: About
71−> ---
72−> Nullam imperdiet sodales orci vitae molestie.
73−> Nunc quam orci, pharetra a rhoncus vitae,
74−> eleifend id felis. Suspendisse potenti...
69+ ---
70+ title: About
71+ ---
72+ Nullam imperdiet sodales orci vitae molestie.
73+ Nunc quam orci, pharetra a rhoncus vitae,
74+ eleifend id felis. Suspendisse potenti...
7575
7676This contains one `key: value` pair, namely `title: About`. The rest of the
7777file is treated as markdown by pandoc. If you want to know more about
@@ -209,4 +209,4 @@ questions, feel free to ask them on the
209209[google discussion group](http://groups.google.com/group/hakyll).
210210
211211If you feel comfortable with the basics, here is a next tutorial:
212−[building a simple blog](tutorial2.html).
212+[more about writing pages](tutorial2.html).
examples/hakyll/tutorial2.markdown+109−112
@@ -1,162 +1,159 @@
11---
22title: Tutorial (Part II)
3−what: creates a simple blog
3+what: elaborates a little on writing pages and templates
44---
55
6−## Creating a simple blog with Hakyll
6+## The structure of a Page
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+The most important thing to realize is that a `Page` is just a key-value
9+mapping. Another example:
1010
11−A [zip file containing the source](examples/simpleblog.zip) for this
12−tutorial is also available.
11+ ---
12+ title: About
13+ author: Mia Wallace
14+ ---
15+ Hello there! This is
16+ a simple about page.
1317
14−Blogs, as you probably know, are composed of posts. In Hakyll, we're going
15−to use simple pages for posts. All posts are located in the `posts`
16−directory. But we're not going to use the `directory` command here - you will
17−see why later. First, some trivial things like css.
18+This will produce the following mapping:
1819
19−~~~~~{.haskell}
20−main = hakyll $ do
21− directory css "css"
22−~~~~~
20+- `title`: About
21+- `author`: Mia Wallace
22+- `body`: Hello there! This is a simple about page.
2323
24−## Finding the posts, and a bit about renderables
24+`body` is the traditional name for the main body part of a page. If the page has
25+a `.markdown` extension for example, this would also be rendered by pandoc. But
26+pages are more flexible. The following is also a valid page:
2527
26−`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
27−provide us with all the blog posts. The blog posts have a
28−`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
29−can sort them easily, you could of course name them whatever you want. They
30−contain some metadata, too:
28+ Hello there! This is
29+ a simple about page.
3130
32−> title: A first post
33−> author: Julius Caesar
34−> date: November 5, 2009
35−> ---
36−> Lorem ipsum dolor sit amet, consectetur adipiscing elit.
37−> Vivamus pretium leo adipiscing lectus iaculis lobortis.
38−> Vivamus scelerisque velit dignissim metus...
31+This will produce one key-value pair:
3932
40−Now, we find the posts and sort them reversed:
33+- `body`: Hello there! This is a simple about page.
4134
42−~~~~~{.haskell}
43−-- Find all post paths.
44−postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
45−~~~~~
35+But the `Page` parser can do more than this. You can add extra sections, apart
36+from the body, and even leave out the body.
37+
38+ ---
39+ author: Vincent Vega
40+
41+ --- prelude
42+ A small introduction goes here. I can write *markdown* here, by the way. Well,
43+ assuming this page has a `.markdown` extension.
44+
45+ --- main
46+ I can write some more things here.
47+
48+This will produce the following:
49+
50+- `author`: Vincent Vega
51+- `prelude`: A small introduction goes here. I can write *markdown* here, by the
52+ way. Well, assuming this page has a `.markdown` extension.
53+- `main`: I can write some more things here.
54+
55+The example from this tutorial (we will see later) uses this to build a
56+three-column system for the website, separating content from layout.
57+
58+## Combining pages
4659
47−Our `postPaths` value is now of the type `[FilePath]`. `FilePath` is no
48−instance of `Renderable`, but `PagePath` is:
60+Now you know that `Page`s, and `Renderable`s in general, are basically nothing
61+more than key-values mappings, it is time to abuse this fact. There is another
62+`Renderable` type we haven't talked about before: a `CombinedRenderable`.
63+
64+The type signature of the `combine` function does a pretty good job at
65+explaining it:
4966
5067~~~~~{.haskell}
51−let renderablePosts = map createPagePath postPaths
68+combine :: (Renderable a, Renderable b)
69+ => a -> b -> CombinedRenderable a b
5270~~~~~
5371
54−We have two templates we want to render our posts with: first we would like to
55−render them using `templates/post.html`, and we want to render the result
56−using `templates/default.html`. This can be done with the `renderChain`
57−function:
72+This means we can take two `Renderable` values and combine them. This is
73+basically a `Map.union`: The result will contain all keys from `a`, and all
74+keys from `b`. If a key is present in both `Renderable`s, the value from `a`
75+will be chosen. This is, for example, always the case with an `url` (since
76+all `Renderable` types always have an url).
77+
78+Combining two `Renderable`s, but setting a different `url` is quite common, so
79+there is another function that helps us here:
5880
5981~~~~~{.haskell}
60−mapM_ (renderChain [ "templates/post.html"
61− , "templates/default.html"
62− ]) renderablePosts
82+combineWithURL :: (Renderable a, Renderable b)
83+ => FilePath -> a -> b -> CombinedRenderable a b
6384~~~~~
6485
65−Remember that the `renderChain` works by rendering the datatype using the first
66−template, creating a new page with the render result in the `body` field, and so
67−on until it has been rendered with all templates.
86+## The example
6887
69−Now, we have the posts rendered. What is left is to generate some kind of index
70−page with links to those posts. We want one general list showing all posts, and
71−we want to show a few recent posts on the index page.
88+Now that we have the tools, we'll get on to the example. This time, we'll
89+be making a more advanced brochure site. Here
90+[is a zip file](examples/morepages.zip) containing the source code for the
91+tutorial.
7292
73−## Custom Pages
93+Every page consists of three sections, originally named `section1`, `section2`
94+and `section3`. So our pages look more or less like this:
7495
75−Currently, there are 3 renderable datatypes in Hakyll:
96+ ---
97+ title: About
7698
77−- `Page`: The result of any rendering action. It is generally not recommended
78− to use pages a lot, because they cannot check dependencies (and therefore,
79− you would always regenerate your entire site if you use pages the wrong way).
80−- `PagePath`: Basically just a `FilePath` in a box. Internally, this will use
81− a `Page` for rendering, but `PagePath` provides better dependency checking
82− and works on a higher level.
83−- `CustomPage`: Basically the name says it - the preferred way of creating
84− custom pages in Hakyll.
99+ --- section1
100+ ## Mattis
101+ Nullam imperdiet sodales orci vitae molestie. Nunc...
85102
86−We will use a `CustomPage` here. Basically, all `Renderable` datatypes are in
87−the end just `key: value` mappings. A CustomPage is created using the
88−`createCustomPage` function, which has the following type signature:
89−
90−~~~~~{.haskell}
91−createCustomPage :: FilePath
92− -> [FilePath]
93− -> [(String, Either String (IO String)]
94−~~~~~
103+ --- section2
104+ ## Orci
105+ Vivamus eget mauris sit amet nulla laoreet lobortis. Nulla in...
95106
96−The first argument is the `url` of the page to generate. For our index page,
97−this will be, `index.html`. The second argument is _a list of dependencies_.
98−Basically, you should here give a list of files on which your custom page
99−depends.
107+ --- section3
108+ ## Augue
109+ In urna ante, pulvinar et imperdiet nec, fermentum ac...
100110
101−The last argument is obviously our `key: value` mapping. But why the `Either`?
102−This, once again, is about dependency handling. The idea is that you can choose
103−which type to use for the value:
111+The cool thing is we do not have to specify how these will be layed out. In our
112+template, we decide to use a simple three column system:
104113
105−- `String`: Simply a `String`.
106−- `IO String`: Here, you can give an arbitrary `IO` action that will result
107− in a String. However - this action _will not be executed_ when the file
108− in `_site` is up-to-date.
114+~~~~~{.html}
115+<div class="column"> $section1 </div>
116+<div class="column"> $section2 </div>
117+<div class="column"> $section3 </div>
118+~~~~~
109119
110−First, let us define this `IO String` for our index page. We want to render
111−every post using a simple template:
120+The columns are then floated using css. So far so good, but what if we wanted
121+an additional text block on every page? An easy solution would be to add this
122+to the template, but then our layout-content separation idea will be broken
123+again. So we simply add to the template:
112124
113125~~~~~{.html}
114−<li>
115− <a href="$$root/$url">$title</a>
116− - <em>$date</em> - by <em>$author</em>
117−</li>
126+<div class="footer"> $footer </div>
118127~~~~~
119128
120−When every post is rendered with this template, we then want to concatenate the
121−result. Since rendering and concatenating is pretty common, Hakyll provides us
122−with a high-level function to do this.
129+And now we will use `combine` to put the footer on every page. We write a small
130+auxiliary function that combines a given `Renderable` with the footer:
123131
124132~~~~~{.haskell}
125−let recentPosts = renderAndConcat "templates/postitem.html"
126− (take 3 renderablePosts)
133+withFooter a = a `combine` createPagePath "footer.markdown"
127134~~~~~
128135
129−Now, creating our custom page is fairly straight-forward:
136+Now, were we previously wrote:
130137
131138~~~~~{.haskell}
132−createCustomPage "index.html"
133− ("templates/postitem.html" : take 3 postPaths)
134− [ ("title", Left "All posts")
135− , ("posts", Right recentPosts)
136− ]
139+render "about.markdown"
140+where render = renderChain ["templates/default.html"]
141+ . createPagePath
137142~~~~~
138143
139−You can see our three arguments here. We're rendering `index.html`, then we tell
140−Hakyll on what files it depends - here the `templates/postitem.html` template
141−and the latest 3 posts. Finally, we give a `title` value to substitute in the
142−template, and the result of our concatenation. Of course, we also need to render
143−this custom page:
144+We simply have to add our footer:
144145
145146~~~~~{.haskell}
146−renderChain ["index.html", "templates/default.html"] $
147− createCustomPage "index.html"
148− ("templates/postitem.html" : take 3 postPaths)
149− [ ("title", Left "All posts")
150− , ("posts", Right recentPosts)
151− ]
147+render "about.markdown"
148+where render = renderChain ["templates/default.html"]
149+ . withFooter
150+ . createPagePath
152151~~~~~
153152
154−Note that the `index.html` in the `renderChain` list is also a template.
153+And now every page will include the footer.
155154
156−## That's that
155+## That's all folks
157156
158−If you have any more questions, feel free to ask them on the
157+I hope this tutorial was clear enough to teach you the concepts of pages and
158+combining renderables. As always, questions and feedback are welcome at the
159159[google discussion group](http://groups.google.com/group/hakyll).
160−
161−There is a [next tutorial](tutorial3.html), explaining how to add an RSS feed
162−to our sample blog.
examples/hakyll/tutorial3.markdown+130−92
@@ -1,124 +1,162 @@
11---
22title: Tutorial (Part III)
3−what: adds an RSS feed to the blog from the previous tutorial
3+what: creates a simple blog
44---
55
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>
30−~~~~~
6+## Creating a simple blog with Hakyll
317
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>
45−~~~~~
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.
4610
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).
11+A [zip file containing the source](examples/simpleblog.zip) for this
12+tutorial is also available.
13+
14+Blogs, as you probably know, are composed of posts. In Hakyll, we're going
15+to use simple pages for posts. All posts are located in the `posts`
16+directory. But we're not going to use the `directory` command here - you will
17+see why later. First, some trivial things like css.
5018
5119~~~~~{.haskell}
52−renderChain ["templates/rss.xml"] rssPage
20+main = hakyll $ do
21+ directory css "css"
5322~~~~~
5423
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).
24+## Finding the posts, and a bit about renderables
25+
26+`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
27+provide us with all the blog posts. The blog posts have a
28+`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
29+can sort them easily, you could of course name them whatever you want. They
30+contain some metadata, too:
5831
59−## Custom pages again
32+ title: A first post
33+ author: Julius Caesar
34+ date: November 5, 2009
35+ ---
36+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
37+ Vivamus pretium leo adipiscing lectus iaculis lobortis.
38+ Vivamus scelerisque velit dignissim metus...
6039
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.
40+Now, we find the posts and sort them reversed:
41+
42+~~~~~{.haskell}
43+-- Find all post paths.
44+postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
45+~~~~~
6346
64−We want to render every post using the following template,
65−`templates/rssitem.xml`:
47+Our `postPaths` value is now of the type `[FilePath]`. `FilePath` is no
48+instance of `Renderable`, but `PagePath` is:
6649
6750~~~~~{.haskell}
68−<item>
69− <title>$title</title>
70− <link>http://example.com/$url</link>
71− <description>$title by $author</description>
72−</item>
51+let renderablePosts = map createPagePath postPaths
7352~~~~~
7453
75−Since we build on the previous example, we still have our `renderablePosts`
76−list. We'll be using it again:
54+We have two templates we want to render our posts with: first we would like to
55+render them using `templates/post.html`, and we want to render the result
56+using `templates/default.html`. This can be done with the `renderChain`
57+function:
7758
7859~~~~~{.haskell}
79−let recentRSSItems = renderAndConcat "templates/rssitem.xml"
80− (take 3 renderablePosts)
60+mapM_ (renderChain [ "templates/post.html"
61+ , "templates/default.html"
62+ ]) renderablePosts
8163~~~~~
8264
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.
65+Remember that the `renderChain` works by rendering the datatype using the first
66+template, creating a new page with the render result in the `body` field, and so
67+on until it has been rendered with all templates.
8668
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.
69+Now, we have the posts rendered. What is left is to generate some kind of index
70+page with links to those posts. We want one general list showing all posts, and
71+we want to show a few recent posts on the index page.
72+
73+## Custom Pages
74+
75+Currently, there are 3 renderable datatypes in Hakyll:
76+
77+- `Page`: The result of any rendering action. It is generally not recommended
78+ to use pages a lot, because they cannot check dependencies (and therefore,
79+ you would always regenerate your entire site if you use pages the wrong way).
80+- `PagePath`: Basically just a `FilePath` in a box. Internally, this will use
81+ a `Page` for rendering, but `PagePath` provides better dependency checking
82+ and works on a higher level.
83+- `CustomPage`: Basically the name says it - the preferred way of creating
84+ custom pages in Hakyll.
85+
86+We will use a `CustomPage` here. Basically, all `Renderable` datatypes are in
87+the end just `key: value` mappings. A CustomPage is created using the
88+`createCustomPage` function, which has the following type signature:
9089
9190~~~~~{.haskell}
92−let rssPage = createCustomPage
93− "rss.xml"
94− ("templates/postitem.html" : take 3 postPaths)
95− [("items", Right recentRSSItems)]
91+createCustomPage :: FilePath
92+ -> [FilePath]
93+ -> [(String, Either String (IO String)]
9694~~~~~
9795
98−## Adding a link to the feed
96+The first argument is the `url` of the page to generate. For our index page,
97+this will be, `index.html`. The second argument is _a list of dependencies_.
98+Basically, you should here give a list of files on which your custom page
99+depends.
99100
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`:
101+The last argument is obviously our `key: value` mapping. But why the `Either`?
102+This, once again, is about dependency handling. The idea is that you can choose
103+which type to use for the value:
103104
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−~~~~~~
105+- `String`: Simply a `String`.
106+- `IO String`: Here, you can give an arbitrary `IO` action that will result
107+ in a String. However - this action _will not be executed_ when the file
108+ in `_site` is up-to-date.
114109
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`.
110+First, let us define this `IO String` for our index page. We want to render
111+every post using a simple template:
118112
119−## That's it!
113+~~~~~{.html}
114+<li>
115+ <a href="$$root/$url">$title</a>
116+ - <em>$date</em> - by <em>$author</em>
117+</li>
118+~~~~~
119+
120+When every post is rendered with this template, we then want to concatenate the
121+result. Since rendering and concatenating is pretty common, Hakyll provides us
122+with a high-level function to do this.
120123
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
124+~~~~~{.haskell}
125+let recentPosts = renderAndConcat ["templates/postitem.html"]
126+ (take 3 renderablePosts)
127+~~~~~
128+
129+Now, creating our custom page is fairly straight-forward:
130+
131+~~~~~{.haskell}
132+createCustomPage "index.html"
133+ ("templates/postitem.html" : take 3 postPaths)
134+ [ ("title", Left "All posts")
135+ , ("posts", Right recentPosts)
136+ ]
137+~~~~~
138+
139+You can see our three arguments here. We're rendering `index.html`, then we tell
140+Hakyll on what files it depends - here the `templates/postitem.html` template
141+and the latest 3 posts. Finally, we give a `title` value to substitute in the
142+template, and the result of our concatenation. Of course, we also need to render
143+this custom page:
144+
145+~~~~~{.haskell}
146+renderChain ["index.html", "templates/default.html"] $
147+ createCustomPage "index.html"
148+ ("templates/postitem.html" : take 3 postPaths)
149+ [ ("title", Left "All posts")
150+ , ("posts", Right recentPosts)
151+ ]
152+~~~~~
153+
154+Note that the `index.html` in the `renderChain` list is also a template.
155+
156+## That's that
157+
158+If you have any more questions, feel free to ask them on the
124159[google discussion group](http://groups.google.com/group/hakyll).
160+
161+There is a [next tutorial](tutorial4.html), explaining how to add an RSS feed
162+to our sample blog.
examples/hakyll/tutorial4.markdown+90−177
@@ -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).
examples/hakyll/tutorial5.markdownnew file+211
@@ -0,0 +1,211 @@
1+---
2+title: Tutorial (Part V)
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+ -> (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
58+~~~~~
59+
60+So we could use or title manipulation like this:
61+
62+~~~~~{.haskell}
63+renderChainWith titleUpper ["templates/default.html"]
64+ (createPagePath "index.markdown")
65+~~~~~
66+
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`.
74+
75+~~~~~{.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
123+~~~~~
124+
125+Our "render all posts" action can now be written as:
126+
127+~~~~~{.haskell}
128+renderPostList "posts.html" "All posts" postPaths
129+~~~~~
130+
131+## Tag links
132+
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:
137+
138+~~~~~{.haskell}
139+tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
140+~~~~~
141+
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`.
147+
148+~~~~~{.haskell}
149+postManipulation :: ContextManipulation
150+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
151+ . renderTagLinks tagToURL
152+~~~~~
153+
154+If we click a tag, we get a `404`. That's because we haven't generated the
155+post lists for every tag.
156+
157+## The Tag Map
158+
159+Hakyll provides a function called `readTagMap`. Let's inspect it's type.
160+
161+~~~~~{.haskell}
162+readTagMap [FilePath] -> IO Map String [FilePath]
163+~~~~~
164+
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.
167+
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
179+
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.
182+
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+~~~~~
206+
207+## That's it
208+
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
211+[google discussion group](http://groups.google.com/group/hakyll).
examples/morepages/about.markdownnew file+27
@@ -0,0 +1,27 @@
1+---
2+title: About
3+
4+--- section1
5+## Mattis
6+Nullam imperdiet sodales orci vitae molestie. Nunc quam orci, pharetra a
7+rhoncus vitae, eleifend id felis. Suspendisse potenti. Etiam vitae urna orci.
8+Quisque pellentesque dignissim felis, egestas tempus urna luctus vitae. In hac
9+habitasse platea dictumst. Morbi fringilla mattis odio, et mattis tellus
10+accumsan vitae.
11+
12+--- section2
13+## Orci
14+Vivamus eget mauris sit amet nulla laoreet lobortis. Nulla in diam elementum
15+risus convallis commodo. Cras vehicula varius dui vitae facilisis. Proin
16+elementum libero eget leo aliquet quis euismod orci vestibulum. Duis rhoncus
17+lorem consequat tellus vestibulum aliquam. Quisque orci orci, malesuada porta
18+blandit et, interdum nec magna.
19+
20+--- section3
21+## Augue
22+In urna ante, pulvinar et imperdiet nec, fermentum ac tortor. Cras tristique
23+pellentesque euismod. Pellentesque est ante, sagittis vitae vehicula vitae,
24+ullamcorper eget lectus. Curabitur egestas accumsan leo, ac ullamcorper nibh
25+tincidunt id. Curabitur lorem libero, fermentum non tincidunt ac, pretium in
26+libero. Donec vel mi eu tortor accumsan dictum ut in augue. Vestibulum est
27+lorem, bibendum eu vehicula eu, convallis eget mauris.
examples/morepages/css/default.cssnew file+27
@@ -0,0 +1,27 @@
1+body {
2+ width: 600px;
3+ margin: 0px auto 0px auto;
4+}
5+
6+div#navigation {
7+ text-align: center;
8+ border-bottom: 4px solid black;
9+}
10+
11+div#navigation a {
12+ color: white;
13+ text-decoration: none;
14+ background-color: black;
15+ padding: 3px 10px 3px 10px;
16+ margin: 0px 10px 0px 10px;
17+}
18+
19+div.column {
20+ width: 30%;
21+ float: left;
22+ margin: 0px 10px 0px 10px;
23+}
24+
25+div#footer {
26+ clear: both;
27+}
examples/morepages/footer.markdownnew file+16
@@ -0,0 +1,16 @@
1+--- footer
2+## Sapien
3+
4+In hac habitasse platea dictumst. Cras placerat felis nec risus varius in
5+accumsan sem fermentum. Vestibulum elementum aliquam tortor semper vulputate.
6+Vivamus tincidunt tellus sed purus tempor fringilla. Morbi dui nisl, eleifend
7+non dictum vitae, luctus eu lacus. Duis vitae lacus sem, ut porta mauris.
8+Aenean sed ultricies dui. Vivamus ullamcorper metus lorem, at ornare nibh.
9+Mauris mi metus, convallis id lobortis vitae, interdum quis felis. Cras elit
10+massa, pellentesque sit amet pharetra ut, volutpat in arcu. Vivamus blandit,
11+ligula et ultricies consequat, metus sem congue quam, ac pretium enim velit at
12+tortor. Cras in tellus eu sapien pulvinar sollicitudin eu id ipsum. Mauris nec
13+urna tellus, et scelerisque tellus. Nunc imperdiet felis nec libero consectetur
14+tristique tristique ipsum sodales. Cras tortor nisl, condimentum in
15+pellentesque id, interdum vel mi. Suspendisse auctor vehicula orci at
16+scelerisque. Vivamus quis sagittis felis.
examples/morepages/hakyll.hsnew file+13
@@ -0,0 +1,13 @@
1+import Text.Hakyll (hakyll, defaultHakyllConfiguration)
2+import Text.Hakyll.File (directory)
3+import Text.Hakyll.Render (css, static, renderChain)
4+import Text.Hakyll.Renderables (createPagePath, combine)
5+
6+main = hakyll defaultHakyllConfiguration $ do
7+ directory css "css"
8+ render "about.markdown"
9+ render "index.markdown"
10+ render "products.markdown"
11+ where
12+ render = renderChain ["templates/default.html"] . withFooter . createPagePath
13+ withFooter a = a `combine` createPagePath "footer.markdown"
examples/morepages/index.markdownnew file+24
@@ -0,0 +1,24 @@
1+---
2+title: Home
3+
4+--- section1
5+## Purus
6+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempor, urna et
7+auctor tincidunt, eros mauris facilisis purus, eget sollicitudin leo massa sit
8+amet ipsum. Vivamus eu massa in urna vehicula rutrum eget sit amet purus.
9+
10+--- section2
11+## Ligula
12+Mauris sit amet justo mi. Curabitur vel quam felis. In hac habitasse platea
13+dictumst. Etiam nec consequat risus. Donec consequat est vitae neque fermentum
14+feugiat nec ac nibh. Nulla rhoncus, odio quis scelerisque rutrum, metus sem
15+tempor ante, a ornare ipsum felis sed ligula. Morbi urna lectus, scelerisque
16+non pharetra in, rutrum quis ligula. Phasellus semper ullamcorper arcu eu
17+auctor.
18+
19+--- section3
20+## Justo
21+Aliquam sagittis tincidunt libero ut elementum. Ut sit amet vestibulum metus.
22+Ut aliquet congue neque eu tincidunt. Integer eu elit sed massa sollicitudin
23+vehicula nec ut sem. Cras euismod enim eget purus lacinia non feugiat urna
24+imperdiet. Aliquam justo sem, viverra eu vehicula vitae, imperdiet vel magna.
examples/morepages/products.markdownnew file+23
@@ -0,0 +1,23 @@
1+---
2+title: Products
3+
4+--- section1
5+## Lacus
6+Etiam condimentum auctor semper. Donec lobortis, magna id sodales sollicitudin,
7+lectus mi egestas nulla, pulvinar lobortis nunc eros id nisl. Curabitur
8+imperdiet, erat at accumsan vulputate, purus nunc blandit nulla, dictum
9+vestibulum sem lorem eget ipsum.
10+
11+--- section2
12+## Vitae
13+Integer ut dui eu felis mollis vestibulum. Etiam at nibh id diam aliquet
14+vestibulum sit amet a nibh. Aliquam erat volutpat. Etiam vitae nulla at dolor
15+fringilla tempor ut a nunc. Pellentesque elementum elit lorem. Quisque nec
16+ligula ipsum. Nunc augue lacus, ullamcorper vel dapibus in, mattis eget elit.
17+
18+--- section3
19+## Feugiat
20+Pellentesque enim dui, interdum elementum vehicula luctus, feugiat vitae arcu.
21+Vestibulum ut felis justo. Quisque vestibulum mauris eget ipsum luctus
22+consequat. Nunc tincidunt, turpis ut fermentum dapibus,
23+justo tortor bibendum sem, at facilisis justo odio luctus lectus.
examples/morepages/templates/default.htmlnew file+22
@@ -0,0 +1,22 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5+ <head>
6+ <title>MyAweSomeCompany - $title</title>
7+ <link rel="stylesheet" type="text/css" href="$root/css/default.css" />
8+ </head>
9+ <body>
10+ <h1>MyAweSomeCompany - $title</h1>
11+ <div id="navigation">
12+ <a href="$root/index.html">Home</a>
13+ <a href="$root/about.html">About</a>
14+ <a href="$root/products.html">Products</a>
15+ </div>
16+
17+ <div class="column"> $section1 </div>
18+ <div class="column"> $section2 </div>
19+ <div class="column"> $section3 </div>
20+ <div id="footer"> $footer </div>
21+ </body>
22+</html>