Updated README and cabal file for hakyll-0.2.

This commit is contained in:
Jasper Van der Jeugt 2009-12-13 11:22:51 +01:00
parent a18789a6fb
commit d72f11937b
2 changed files with 95 additions and 55 deletions

View file

@ -1,8 +1,8 @@
# Hakyll
# Hayll
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
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.
@ -19,6 +19,7 @@ be generated. In the rest of this document, we will examine a small example.
This is our directory layout:
|-- _cache
|-- _site
|-- favicon.ico
|-- hakyll.hs
@ -29,12 +30,16 @@ This is our directory layout:
| `-- sample.html
`-- text.markdown
The `_cache` and `_site` directories will be automatically created by hakyll.
## Static files
Static files can be rendered using the `static` command. This command ensures
the files will copied when you compile the site.
Static files can be rendered using the `static` function. This function
ensures the files will copied when you compile the site. Hakyll is smart enough
to know when files have changed, and will check the modification time of a file
before copying.
For convenience reasons, there is also a `staticDirectory` command, which works
For convenience reasons, there is also a `staticDirectory` function, which works
recursively.
main = do
@ -44,9 +49,8 @@ recursively.
## 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.
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
@ -61,11 +65,11 @@ mappings.
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:
Metadata is always placed in the header 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`
@ -79,7 +83,7 @@ In hakyll, there is a strict separation between pages and templates. Templates,
for example, cannot contain metadata.
<h2> $title </h2>
by <strong> $author </title>
by <strong> $author </strong>
$body
@ -87,16 +91,18 @@ 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:
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`:
rendered <- render "templates/sample.html" page
writePage rendered
This reads in `text.markdown`, renders it and writes it to the site destination
(`_site/text.html`). The result of a `render` action is an `IO Page`, the
metadata will be copied from the original page, and the body will be replaced by
the rendering result. This means we can combine rendering actions. Given another
template `templates/default.html`:
<html>
<head>
<title> $title </title>
@ -106,29 +112,39 @@ rendering actions. Given another template `templates/default.html`:
</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:
We can now combine the rendering actions (I use `>>=` notation here):
readPage "text.markdown" >>=
renderPage "templates/sample.html" page >>=
renderAndWrite "templates/default.html"
render "templates/sample.html" >>=
render "templates/default.html" >>=
writePage
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.
Jolly good fun and all that, but you can imagine that when we render over nine
thousand posts, our generator will be busy for a while. That's why we have the
`depends` function. It takes a url and a list of dependencies as arguments, and
an IO action. The trick is that this IO action will only be executed if any of
the dependencies is newer than the url given. In our example, we would write:
depends "text.html" ["text.markdown", "templates/sample.html", "templates/default.html"]
(readPage "text.markdown" >>= render "templates/sample.html" >>=
render "templates/default.html" >>= writePage)
Not exactly the prettiest code I've ever seen. Because rendering a page with a
number of templates is very common, there's a `renderChain` function to do this
for us. The above can be replaced by
renderChain ["templates/sample.html", "templates/default.html"] $
createPagePath "text.markdown"
The `renderChain` function will automatically check dependencies and write the
page. In fact, it is recommended that you __always__ use this, even when there's
only one template in the chain.
## 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.
documentation of `Text.Hakyll.Page`. As a more advanced example, I will explain
the RSS system I wrote for my website.
|-- generate.hs
|-- posts
@ -137,24 +153,25 @@ explain the RSS system I wrote for my website.
|-- rss.xml
`-- rssitem.xml
Our post contains some metadata:
Our post contains some metadata:</p
---
title: A first post
date: December 2, 2009
about: A post describing the why and the how of the technical setup of this blog.
---
# 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:
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>
<description> $about </description>
</item>
Now a template for rendering the whole rss feed, `templates/rss.xml`:
@ -169,16 +186,39 @@ Now a template for rendering the whole rss feed, `templates/rss.xml`:
</channel>
</rss>
Alright, let's get coding.
Alright, let's get coding. We first want a list of all posts, sorted so the most
recent entries are first in the list.
-- 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.
Note that this sorting works because the posts have a
`yyyy-mm-dd-title.extension` naming scheme. We want the paths as `PagePath` and
not as `FilePath` or `String`, because `PagePath` is an instance of
`Renderable`. Also, we only want the 5 most recent posts.
let renderablePosts = map createPagePath postPaths
let recentItems = renderAndConcat "templates/postitem.html" $ take 5 renderablePosts
The `renderAndConcat` function takes a template and a list of `Renderable`
items. It renders all renderables with the given template, and concatenates the
result. Note that the concating and reading of pages is not executed yet,
because of laziness. This helps us, since we want to use the modification
timestamps of files, and not render everything every time.
let rssPage = createCustomPage "rss.xml"
("templates/rssitem.xml" : postPaths) [("items", Right recentItems)]
We now created the custom rss page. The `createCustomPage` is a function that
produces a `CustomPage`, which is an instance of `Renderable`. `"rss.xml"` is
our destination url. We then give a list of extra dependencies that were used
to generate the custom page, so Hakyll can check modification stamps. The last
argument is the key-value mapping of our `CustomPage`. Note that the type for
values is `Either String (IO ByteString)`. So, we can either give a simple
string, or an IO action that results in a `ByteString`. The benefit of this is
that the IO action will not be executed if the page in `_site/` is already
up-to-date. Now, we only need to render it using our rss template.
renderChain ["templates/rss.xml"] rssPage
That's it. Now we have a rss feed that is generated only when it is not already
up-to-date.

View file

@ -1,5 +1,5 @@
Name: hakyll
Version: 0.1
Version: 0.2
Synopsis: A simple static site generator library.
Description: