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
144
README.markdown
144
README.markdown
|
@ -1,8 +1,8 @@
|
||||||
# Hakyll
|
# Hayll
|
||||||
|
|
||||||
Hakyll is a simple static site generator library in Haskell. It is mostly
|
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
|
inspired by [Jekyll](http://github.com/mojombo/jekyll), but I like to
|
||||||
it is simpler. An example site where it is used is
|
believe it is simpler. An example site where it is used is
|
||||||
[my personal homepage](http://jaspervdj.be) of which
|
[my personal homepage](http://jaspervdj.be) of which
|
||||||
[the source code](http://jaspervdj.be/snapshot.tar.gz) is available as a
|
[the source code](http://jaspervdj.be/snapshot.tar.gz) is available as a
|
||||||
reference.
|
reference.
|
||||||
|
@ -19,6 +19,7 @@ be generated. In the rest of this document, we will examine a small example.
|
||||||
|
|
||||||
This is our directory layout:
|
This is our directory layout:
|
||||||
|
|
||||||
|
|-- _cache
|
||||||
|-- _site
|
|-- _site
|
||||||
|-- favicon.ico
|
|-- favicon.ico
|
||||||
|-- hakyll.hs
|
|-- hakyll.hs
|
||||||
|
@ -29,12 +30,16 @@ This is our directory layout:
|
||||||
| `-- sample.html
|
| `-- sample.html
|
||||||
`-- text.markdown
|
`-- text.markdown
|
||||||
|
|
||||||
|
The `_cache` and `_site` directories will be automatically created by hakyll.
|
||||||
|
|
||||||
## Static files
|
## Static files
|
||||||
|
|
||||||
Static files can be rendered using the `static` command. This command ensures
|
Static files can be rendered using the `static` function. This function
|
||||||
the files will copied when you compile the site.
|
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.
|
recursively.
|
||||||
|
|
||||||
main = do
|
main = do
|
||||||
|
@ -44,9 +49,8 @@ recursively.
|
||||||
|
|
||||||
## Pages
|
## Pages
|
||||||
|
|
||||||
Pages can be written in html, markdown, LaTeX, and basically anything
|
Pages can be written in html, markdown, LaTeX, and basically anything pandoc
|
||||||
pandoc supports. They can also contain metadata, which are always key-value
|
supports. They can also contain metadata, which are always key-value mappings.
|
||||||
mappings.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
author: Jasper Van der Jeugt
|
author: Jasper Van der Jeugt
|
||||||
|
@ -61,11 +65,11 @@ mappings.
|
||||||
main = putStrLn "Hello World!"
|
main = putStrLn "Hello World!"
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
Metadata is always placed in the beginning of a file, and is delimited by a
|
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
|
string. The metadata can only contain simple key-value pairs. We can now read
|
||||||
now read in this page using the `Text.Hakyll.Page.readPage` function. This
|
in this page using the `Text.Hakyll.Page.readPage` function. This will return a
|
||||||
will return a `Page`, which is actually just a `Map String ByteString`. In
|
`Page`, which is actually just a `Map String ByteString`. In this example, the
|
||||||
this example, the map would consist of the following key-value pairs:
|
map would consist of the following key-value pairs:
|
||||||
|
|
||||||
- `author`: `Jasper Van der Jeugt`
|
- `author`: `Jasper Van der Jeugt`
|
||||||
- `title`: `A sample markdown post`
|
- `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.
|
for example, cannot contain metadata.
|
||||||
|
|
||||||
<h2> $title </h2>
|
<h2> $title </h2>
|
||||||
by <strong> $author </title>
|
by <strong> $author </strong>
|
||||||
|
|
||||||
$body
|
$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
|
that in your template, you can use `$identifier`, and it will be replaced by
|
||||||
the value of `identifier`.
|
the value of `identifier`.
|
||||||
|
|
||||||
With this template we could, for example, render the file we saw in the previous
|
With this template we could, for example, render the file we saw in the
|
||||||
section. It would go like this:
|
previous section. It would go like this:
|
||||||
|
|
||||||
page <- readPage "text.markdown"
|
page <- readPage "text.markdown"
|
||||||
render <- renderPage "templates/sample.html" page
|
rendered <- render "templates/sample.html" page
|
||||||
|
writePage rendered
|
||||||
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`:
|
|
||||||
|
|
||||||
|
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>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title> $title </title>
|
<title> $title </title>
|
||||||
|
@ -106,29 +112,39 @@ rendering actions. Given another template `templates/default.html`:
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
We can now combine the rendering actions:
|
We can now combine the rendering actions (I use `>>=` notation here):
|
||||||
|
|
||||||
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" >>=
|
readPage "text.markdown" >>=
|
||||||
renderPage "templates/sample.html" page >>=
|
render "templates/sample.html" >>=
|
||||||
renderAndWrite "templates/default.html"
|
render "templates/default.html" >>=
|
||||||
|
writePage
|
||||||
|
|
||||||
Now, where will this file be written? In `_site/text.html`, of course! That's
|
Jolly good fun and all that, but you can imagine that when we render over nine
|
||||||
because the page still contains a key called `url`, which the renderAndWrite
|
thousand posts, our generator will be busy for a while. That's why we have the
|
||||||
function uses to determine the file destination.
|
`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
|
## More advanced things
|
||||||
|
|
||||||
Sometimes, you want to create a `Page` from scratch, without reading from a
|
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
|
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
|
documentation of `Text.Hakyll.Page`. As a more advanced example, I will explain
|
||||||
explain the RSS system I wrote for my website.
|
the RSS system I wrote for my website.
|
||||||
|
|
||||||
|-- generate.hs
|
|-- generate.hs
|
||||||
|-- posts
|
|-- posts
|
||||||
|
@ -137,24 +153,25 @@ explain the RSS system I wrote for my website.
|
||||||
|-- rss.xml
|
|-- rss.xml
|
||||||
`-- rssitem.xml
|
`-- rssitem.xml
|
||||||
|
|
||||||
Our post contains some metadata:
|
Our post contains some metadata:</p
|
||||||
|
|
||||||
---
|
---
|
||||||
title: A first post
|
title: A first post
|
||||||
date: December 2, 2009
|
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
|
||||||
|
|
||||||
A first post describing the technical setup of this blog, for that is
|
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
|
The `templates/rssitem.xml` file is a template for rendering one post to an rss
|
||||||
rss item:
|
item:
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<title> $title </title>
|
<title> $title </title>
|
||||||
<link>http://jaspervdj.be/$url</link>
|
<link>http://jaspervdj.be/$url</link>
|
||||||
<description>New blogpost: $title</description>
|
<description> $about </description>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
Now a template for rendering the whole rss feed, `templates/rss.xml`:
|
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>
|
</channel>
|
||||||
</rss>
|
</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"
|
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
|
Name: hakyll
|
||||||
Version: 0.1
|
Version: 0.2
|
||||||
|
|
||||||
Synopsis: A simple static site generator library.
|
Synopsis: A simple static site generator library.
|
||||||
Description:
|
Description:
|
||||||
|
|
Loading…
Reference in a new issue