summaryrefslogtreecommitdiff
path: root/README.markdown
blob: 4bc14013144f2e43cf8412bd7e4a23dc08da8a92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# Hakyll

Hakyll is a simple static site generator library in Haskell. It is mostly
inspired by [Jekyll](http://github.com/mojombo/jekyll), but I like to believe
it is simpler. An example site where it is used is
[my personal homepage](http://jaspervdj.be) of which
[the source code](http://jaspervdj.be/snapshot.tar.gz) is available as a
reference.

## Installation

    cabal install hakyll

## Configuration

Inspired by [xmonad](http://xmonad.org), 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 was `text.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.