Updated README and cabal file for hakyll-0.2.
This commit is contained in:
parent
a18789a6fb
commit
d72f11937b
2 changed files with 95 additions and 55 deletions
148
README.markdown
148
README.markdown
|
@ -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,29 +153,30 @@ 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>
|
||||
<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`:
|
||||
|
||||
<?xml version="1.0" ?>
|
||||
<?xml version="1.0"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>jaspervdj - a personal blog</title>
|
||||
|
@ -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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Name: hakyll
|
||||
Version: 0.1
|
||||
Version: 0.2
|
||||
|
||||
Synopsis: A simple static site generator library.
|
||||
Description:
|
||||
|
|
Loading…
Reference in a new issue