summaryrefslogtreecommitdiff
path: root/README.markdown
diff options
context:
space:
mode:
authorJasper Van der Jeugt <jaspervdj@gmail.com>2009-12-13 11:22:51 +0100
committerJasper Van der Jeugt <jaspervdj@gmail.com>2009-12-13 11:22:51 +0100
commitd72f11937bd4e2425fd8b1ad9659364811291753 (patch)
tree977d93f5e6bd1b055b918bb776aa4bfdb2626884 /README.markdown
parenta18789a6fbc1cff2a3828a626769cc96a8526026 (diff)
downloadhakyll-d72f11937bd4e2425fd8b1ad9659364811291753.tar.gz
Updated README and cabal file for hakyll-0.2.
Diffstat (limited to 'README.markdown')
-rw-r--r--README.markdown148
1 files changed, 94 insertions, 54 deletions
diff --git a/README.markdown b/README.markdown
index 4bc1401..51fa5d8 100644
--- a/README.markdown
+++ b/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:
+We can now combine the rendering actions (I use `>>=` notation here):
+ readPage "text.markdown" >>=
+ render "templates/sample.html" >>=
+ render "templates/default.html" >>=
+ writePage
+
+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:
- page <- readPage "text.markdown"
- render <- (renderPage "templates/sample.html" page >>=
- renderPage "templates/default.html")
+ depends "text.html" ["text.markdown", "templates/sample.html", "templates/default.html"]
+ (readPage "text.markdown" >>= render "templates/sample.html" >>=
+ render "templates/default.html" >>= writePage)
-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:
+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
- readPage "text.markdown" >>=
- renderPage "templates/sample.html" page >>=
- renderAndWrite "templates/default.html"
+ renderChain ["templates/sample.html", "templates/default.html"] $
+ createPagePath "text.markdown"
-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.
+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.