src/Text/Hakyll | ||
hakyll.cabal | ||
LICENSE | ||
README.markdown | ||
Setup.hs |
Hakyll
Hakyll is a simple static site generator library in Haskell. It is mostly inspired by Jekyll, but I like to believe it is simpler. An example site where it is used is my personal homepage of which the source code is available as a reference.
Installation
cabal install hakyll
Configuration
Inspired by xmonad, a small Haskell program is used as configuration file. In this file, you give instructions on how the site should be generated. In the rest of this document, we will examine a small example.
This is our directory layout:
|-- _site
|-- favicon.ico
|-- hakyll.hs
|-- images
| `-- foo.png
|-- templates
| |-- default.html
| `-- sample.html
`-- text.markdown
Static files
Static files can be rendered using the static
command. This command ensures
the files will copied when you compile the site.
For convenience reasons, there is also a staticDirectory
command, which works
recursively.
main = do
static "favicon.ico"
staticDirectory "images"
staticDirectory "css"
Pages
Pages can be written in html, markdown, LaTeX, and basically anything pandoc supports. They can also contain metadata, which are always key-value mappings.
---
author: Jasper Van der Jeugt
title: A sample markdown post
---
# A sample markdown post
This is a sample markdown post. It supports pandoc extensions
like code highlighting. For example:
~~~~{.haskell}
main = putStrLn "Hello World!"
~~~~
Metadata is always placed in the beginning of a file, and is delimited by a
---
string. The metadata can only contain simple key-value pairs. We can
now read in this page using the Text.Hakyll.Page.readPage
function. This
will return a Page
, which is actually just a Map String ByteString
. In
this example, the map would consist of the following key-value pairs:
author
:Jasper Van der Jeugt
title
:A sample markdown post
body
: The rest of the file (rendered to html).url
:text.html
(the original filename wastext.markdown
, the extension was changed to html).
Templates
In hakyll, there is a strict separation between pages and templates. Templates, for example, cannot contain metadata.
<h2> $title </h2>
by <strong> $author </title>
$body
Templates are rendered using the Haskell Text.Template
library. This means
that in your template, you can use $identifier
, and it will be replaced by
the value of identifier
.
With this template we could, for example, render the file we saw in the previous section. It would go like this:
page <- readPage "text.markdown"
render <- renderPage "templates/sample.html" page
Now, render
will be a Page
containing all metadata from page
, but the
body
key would be replaced by the substitution. This means we can combine
rendering actions. Given another template templates/default.html
:
<html>
<head>
<title> $title </title>
</head>
<body>
$body
</body>
</html>
We can now combine the rendering actions:
page <- readPage "text.markdown"
render <- (renderPage "templates/sample.html" page >>=
renderPage "templates/default.html")
Of course, you can't really do anything with the render if you don't write it
to a file somewhere. That's why the function renderAndWrite
exists:
readPage "text.markdown" >>=
renderPage "templates/sample.html" page >>=
renderAndWrite "templates/default.html"
Now, where will this file be written? In _site/text.html
, of course! That's
because the page still contains a key called url
, which the renderAndWrite
function uses to determine the file destination.
More advanced things
Sometimes, you want to create a Page
from scratch, without reading from a
file. There are functions to do that for you, and I suggest you read the
documentation of Text.Hakyll.Page
. As a more advanced example, I will
explain the RSS system I wrote for my website.
|-- generate.hs
|-- posts
| `-- 2009-12-02-a-first-post.markdown
`-- templates
|-- rss.xml
`-- rssitem.xml
Our post contains some metadata:
---
title: A first post
date: December 2, 2009
---
# A first post
A first post describing the technical setup of this blog, for that is
The templates/rssitem.xml
file is a template for rendering one post to an
rss item:
<item>
<title>$title</title>
<link>http://jaspervdj.be/$url</link>
<description>New blogpost: $title</description>
</item>
Now a template for rendering the whole rss feed, templates/rss.xml
:
<?xml version="1.0" ?>
<rss version="2.0">
<channel>
<title>jaspervdj - a personal blog</title>
<link>http://jaspervdj.be/</link>
<description>Personal blog of jaspervdj</description>
$items
</channel>
</rss>
Alright, let's get coding.
-- Find all posts paths.
postPaths <- liftM (L.reverse . L.sort) $ getRecursiveContents "posts"
-- Read and render all posts with the rssitem.xml template
-- Also, only render 5 posts.
pages <- mapM readPage (take 5 postPaths)
items <- mapM (renderPage "templates/rssitem.xml") pages
-- Render the result
renderAndWrite "templates/rss.xml" $ pageFromList [ ("items", concatPages items),
("url", "rss.xml") ]
That's that. Now we have a nice rss feed.