@@ -1,8 +1,8 @@
1−# Hakyll
1+# Hayll
22
33Hakyll is a simple static site generator library in Haskell. It is mostly
4−inspired by [Jekyll](http://github.com/mojombo/jekyll), but I like to believe
5−it is simpler. An example site where it is used is
4+inspired by [Jekyll](http://github.com/mojombo/jekyll), but I like to
5+believe it is simpler. An example site where it is used is
66[my personal homepage](http://jaspervdj.be) of which
77[the source code](http://jaspervdj.be/snapshot.tar.gz) is available as a
88reference.
@@ -19,6 +19,7 @@ be generated. In the rest of this document, we will examine a small example.
1919
2020This is our directory layout:
2121
22+ |-- _cache
2223 |-- _site
2324 |-- favicon.ico
2425 |-- hakyll.hs
@@ -29,12 +30,16 @@ This is our directory layout:
2930 | `-- sample.html
3031 `-- text.markdown
3132
33+The `_cache` and `_site` directories will be automatically created by hakyll.
34+
3235## Static files
3336
34−Static files can be rendered using the `static` command. This command ensures
35−the files will copied when you compile the site.
37+Static files can be rendered using the `static` function. This function
38+ensures the files will copied when you compile the site. Hakyll is smart enough
39+to know when files have changed, and will check the modification time of a file
40+before copying.
3641
37−For convenience reasons, there is also a `staticDirectory` command, which works
42+For convenience reasons, there is also a `staticDirectory` function, which works
3843recursively.
3944
4045 main = do
@@ -44,9 +49,8 @@ recursively.
4449
4550## Pages
4651
47−Pages can be written in html, markdown, LaTeX, and basically anything
48−pandoc supports. They can also contain metadata, which are always key-value
49−mappings.
52+Pages can be written in html, markdown, LaTeX, and basically anything pandoc
53+supports. They can also contain metadata, which are always key-value mappings.
5054
5155 ---
5256 author: Jasper Van der Jeugt
@@ -61,11 +65,11 @@ mappings.
6165 main = putStrLn "Hello World!"
6266 ~~~~
6367
64−Metadata is always placed in the beginning of a file, and is delimited by a
65−`---` string. The metadata can only contain simple key-value pairs. We can
66−now read in this page using the `Text.Hakyll.Page.readPage` function. This
67−will return a `Page`, which is actually just a `Map String ByteString`. In
68−this example, the map would consist of the following key-value pairs:
68+Metadata is always placed in the header of a file, and is delimited by a `---`
69+string. The metadata can only contain simple key-value pairs. We can now read
70+in this page using the `Text.Hakyll.Page.readPage` function. This will return a
71+`Page`, which is actually just a `Map String ByteString`. In this example, the
72+map would consist of the following key-value pairs:
6973
7074- `author`: `Jasper Van der Jeugt`
7175- `title`: `A sample markdown post`
@@ -79,7 +83,7 @@ In hakyll, there is a strict separation between pages and templates. Templates,
7983for example, cannot contain metadata.
8084
8185 <h2> $title </h2>
82− by <strong> $author </title>
86+ by <strong> $author </strong>
8387
8488 $body
8589
@@ -87,16 +91,18 @@ Templates are rendered using the Haskell `Text.Template` library. This means
8791that in your template, you can use `$identifier`, and it will be replaced by
8892the value of `identifier`.
8993
90−With this template we could, for example, render the file we saw in the previous
91−section. It would go like this:
94+With this template we could, for example, render the file we saw in the
95+previous section. It would go like this:
9296
9397 page <- readPage "text.markdown"
94− render <- renderPage "templates/sample.html" page
95−
96−Now, `render` will be a `Page` containing all metadata from `page`, but the
97−`body` key would be replaced by the substitution. This means we can combine
98−rendering actions. Given another template `templates/default.html`:
99−
98+ rendered <- render "templates/sample.html" page
99+ writePage rendered
100+
101+This reads in `text.markdown`, renders it and writes it to the site destination
102+(`_site/text.html`). The result of a `render` action is an `IO Page`, the
103+metadata will be copied from the original page, and the body will be replaced by
104+the rendering result. This means we can combine rendering actions. Given another
105+template `templates/default.html`:
100106 <html>
101107 <head>
102108 <title> $title </title>
@@ -106,29 +112,39 @@ rendering actions. Given another template `templates/default.html`:
106112 </body>
107113 </html>
108114
109−We can now combine the rendering actions:
115+We can now combine the rendering actions (I use `>>=` notation here):
116+ readPage "text.markdown" >>=
117+ render "templates/sample.html" >>=
118+ render "templates/default.html" >>=
119+ writePage
120+
121+Jolly good fun and all that, but you can imagine that when we render over nine
122+thousand posts, our generator will be busy for a while. That's why we have the
123+`depends` function. It takes a url and a list of dependencies as arguments, and
124+an IO action. The trick is that this IO action will only be executed if any of
125+the dependencies is newer than the url given. In our example, we would write:
110126
111− page <- readPage "text.markdown"
112− render <- (renderPage "templates/sample.html" page >>=
113− renderPage "templates/default.html")
127+ depends "text.html" ["text.markdown", "templates/sample.html", "templates/default.html"]
128+ (readPage "text.markdown" >>= render "templates/sample.html" >>=
129+ render "templates/default.html" >>= writePage)
114130
115−Of course, you can't really do anything with the render if you don't write it
116−to a file somewhere. That's why the function `renderAndWrite` exists:
131+Not exactly the prettiest code I've ever seen. Because rendering a page with a
132+number of templates is very common, there's a `renderChain` function to do this
133+for us. The above can be replaced by
117134
118− readPage "text.markdown" >>=
119− renderPage "templates/sample.html" page >>=
120− renderAndWrite "templates/default.html"
135+ renderChain ["templates/sample.html", "templates/default.html"] $
136+ createPagePath "text.markdown"
121137
122−Now, where will this file be written? In `_site/text.html`, of course! That's
123−because the page still contains a key called `url`, which the renderAndWrite
124−function uses to determine the file destination.
138+The `renderChain` function will automatically check dependencies and write the
139+page. In fact, it is recommended that you __always__ use this, even when there's
140+only one template in the chain.
125141
126142## More advanced things
127143
128144Sometimes, you want to create a `Page` from scratch, without reading from a
129145file. There are functions to do that for you, and I suggest you read the
130−documentation of `Text.Hakyll.Page`. As a more advanced example, I will
131−explain the RSS system I wrote for my website.
146+documentation of `Text.Hakyll.Page`. As a more advanced example, I will explain
147+the RSS system I wrote for my website.
132148
133149 |-- generate.hs
134150 |-- posts
@@ -137,29 +153,30 @@ explain the RSS system I wrote for my website.
137153 |-- rss.xml
138154 `-- rssitem.xml
139155
140−Our post contains some metadata:
156+Our post contains some metadata:</p
141157
142158 ---
143159 title: A first post
144160 date: December 2, 2009
161+ about: A post describing the why and the how of the technical setup of this blog.
145162 ---
146163
147164 # A first post
148165
149166 A first post describing the technical setup of this blog, for that is
150167
151−The `templates/rssitem.xml` file is a template for rendering one post to an
152−rss item:
168+The `templates/rssitem.xml` file is a template for rendering one post to an rss
169+item:
153170
154171 <item>
155− <title>$title</title>
172+ <title> $title </title>
156173 <link>http://jaspervdj.be/$url</link>
157− <description>New blogpost: $title</description>
174+ <description> $about </description>
158175 </item>
159176
160177Now a template for rendering the whole rss feed, `templates/rss.xml`:
161178
162− <?xml version="1.0" ?>
179+ <?xml version="1.0"?>
163180 <rss version="2.0">
164181 <channel>
165182 <title>jaspervdj - a personal blog</title>
@@ -169,16 +186,39 @@ Now a template for rendering the whole rss feed, `templates/rss.xml`:
169186 </channel>
170187 </rss>
171188
172−Alright, let's get coding.
189+Alright, let's get coding. We first want a list of all posts, sorted so the most
190+recent entries are first in the list.
173191
174− -- Find all posts paths.
175192 postPaths <- liftM (L.reverse . L.sort) $ getRecursiveContents "posts"
176− -- Read and render all posts with the rssitem.xml template
177− -- Also, only render 5 posts.
178− pages <- mapM readPage (take 5 postPaths)
179− items <- mapM (renderPage "templates/rssitem.xml") pages
180− -- Render the result
181− renderAndWrite "templates/rss.xml" $ pageFromList [ ("items", concatPages items),
182− ("url", "rss.xml") ]
183−
184−That's that. Now we have a nice rss feed.
193+
194+Note that this sorting works because the posts have a
195+`yyyy-mm-dd-title.extension` naming scheme. We want the paths as `PagePath` and
196+not as `FilePath` or `String`, because `PagePath` is an instance of
197+`Renderable`. Also, we only want the 5 most recent posts.
198+
199+ let renderablePosts = map createPagePath postPaths
200+ let recentItems = renderAndConcat "templates/postitem.html" $ take 5 renderablePosts
201+
202+The `renderAndConcat` function takes a template and a list of `Renderable`
203+items. It renders all renderables with the given template, and concatenates the
204+result. Note that the concating and reading of pages is not executed yet,
205+because of laziness. This helps us, since we want to use the modification
206+timestamps of files, and not render everything every time.
207+
208+let rssPage = createCustomPage "rss.xml"
209+ ("templates/rssitem.xml" : postPaths) [("items", Right recentItems)]
210+
211+We now created the custom rss page. The `createCustomPage` is a function that
212+produces a `CustomPage`, which is an instance of `Renderable`. `"rss.xml"` is
213+our destination url. We then give a list of extra dependencies that were used
214+to generate the custom page, so Hakyll can check modification stamps. The last
215+argument is the key-value mapping of our `CustomPage`. Note that the type for
216+values is `Either String (IO ByteString)`. So, we can either give a simple
217+string, or an IO action that results in a `ByteString`. The benefit of this is
218+that the IO action will not be executed if the page in `_site/` is already
219+up-to-date. Now, we only need to render it using our rss template.
220+
221+ renderChain ["templates/rss.xml"] rssPage
222+
223+That's it. Now we have a rss feed that is generated only when it is not already
224+up-to-date.