on master help sign in
Wrote fourth part of the tutorials.
commite7918c39f8378cc5d5df390a0f6a9f60dd3ba1a3 authorJasper Van der Jeugt <jaspervdj@gmail.com> dateFri 12 Mar 2010, 16:46 (17 years ago) parentdabfc43
2 files changed+163−6
examples/hakyll/tutorials/part04.markdown+157 examples/simpleblog/hakyll.hs+6−6
examples/hakyll/tutorials/part04.markdownnew file+157
@@ -0,0 +1,157 @@
1+---
2+title: Creating a Blog
3+what: creates a simple blog
4+---
5+
6+## Creating a simple blog with Hakyll
7+
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.
10+
11+A [zip file] containing the source for this tutorial is also available.
12+
13+[zip file]: 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
107+~~~~~
108+
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+~~~~~
146+
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.
150+
151+## The gist of it
152+
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/simpleblog/hakyll.hs+6−6
@@ -4,29 +4,29 @@ import Text.Hakyll (hakyll)
44import Text.Hakyll.Render
55import Text.Hakyll.Context
66import Text.Hakyll.File (getRecursiveContents, directory)
7−import Text.Hakyll.Renderables (createPagePath, createCustomPage, createListing)
7+import Text.Hakyll.CreateContext (createPage, createCustomPage, createListing)
88import Data.List (sort)
99import Control.Monad (mapM_, liftM)
1010import Control.Monad.Reader (liftIO)
1111import Data.Either (Either(..))
1212
13−main = hakyll $ do
13+main = hakyll "http://example.com" $ do
1414 -- Static directory.
1515 directory css "css"
1616
1717 -- Find all post paths.
1818 postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
19− let renderablePosts = map createPagePath postPaths
19+ let postPages = map createPage postPaths
2020
2121 -- Render index, including recent posts.
22− let index = createListing "index.html" "templates/postitem.html" (take 3 renderablePosts) [("title", "Home")]
22+ let index = createListing "index.html" ["templates/postitem.html"] (take 3 postPages) [("title", Left "Home")]
2323 renderChain ["index.html", "templates/default.html"] index
2424
2525 -- Render all posts list.
26− let posts = createListing "posts.html" "templates/postitem.html" renderablePosts [("title", "All posts")]
26+ let posts = createListing "posts.html" ["templates/postitem.html"] postPages [("title", Left "All posts")]
2727 renderChain ["posts.html", "templates/default.html"] posts
2828
2929 -- Render all posts.
3030 liftIO $ putStrLn "Generating posts..."
31− mapM_ (renderChain ["templates/post.html", "templates/default.html"]) renderablePosts
31+ mapM_ (renderChain ["templates/post.html", "templates/default.html"]) postPages
3232