Wrote another tutorial, and updated some tutorials.

This commit is contained in:
Jasper Van der Jeugt 2010-01-19 17:17:48 +01:00
parent db0eef8470
commit 0ba0eac8d9
12 changed files with 711 additions and 400 deletions

View file

@ -66,12 +66,12 @@ metadata. The metadata is placed in the file header and surrouded by `---`
lines. Each line should contain a `key: value` pair. Let's have a look at the
`about.markdown` page.
> ---
> title: About
> ---
> Nullam imperdiet sodales orci vitae molestie.
> Nunc quam orci, pharetra a rhoncus vitae,
> eleifend id felis. Suspendisse potenti...
---
title: About
---
Nullam imperdiet sodales orci vitae molestie.
Nunc quam orci, pharetra a rhoncus vitae,
eleifend id felis. Suspendisse potenti...
This contains one `key: value` pair, namely `title: About`. The rest of the
file is treated as markdown by pandoc. If you want to know more about
@ -209,4 +209,4 @@ questions, feel free to ask them on the
[google discussion group](http://groups.google.com/group/hakyll).
If you feel comfortable with the basics, here is a next tutorial:
[building a simple blog](tutorial2.html).
[more about writing pages](tutorial2.html).

View file

@ -1,162 +1,159 @@
---
title: Tutorial (Part II)
what: creates a simple blog
what: elaborates a little on writing pages and templates
---
## Creating a simple blog with Hakyll
## The structure of a Page
After we created a simple brochure site, we're going to try something more
advanced: we are going to create a simple blog system.
The most important thing to realize is that a `Page` is just a key-value
mapping. Another example:
A [zip file containing the source](examples/simpleblog.zip) for this
tutorial is also available.
---
title: About
author: Mia Wallace
---
Hello there! This is
a simple about page.
Blogs, as you probably know, are composed of posts. In Hakyll, we're going
to use simple pages for posts. All posts are located in the `posts`
directory. But we're not going to use the `directory` command here - you will
see why later. First, some trivial things like css.
This will produce the following mapping:
- `title`: About
- `author`: Mia Wallace
- `body`: Hello there! This is a simple about page.
`body` is the traditional name for the main body part of a page. If the page has
a `.markdown` extension for example, this would also be rendered by pandoc. But
pages are more flexible. The following is also a valid page:
Hello there! This is
a simple about page.
This will produce one key-value pair:
- `body`: Hello there! This is a simple about page.
But the `Page` parser can do more than this. You can add extra sections, apart
from the body, and even leave out the body.
---
author: Vincent Vega
--- prelude
A small introduction goes here. I can write *markdown* here, by the way. Well,
assuming this page has a `.markdown` extension.
--- main
I can write some more things here.
This will produce the following:
- `author`: Vincent Vega
- `prelude`: A small introduction goes here. I can write *markdown* here, by the
way. Well, assuming this page has a `.markdown` extension.
- `main`: I can write some more things here.
The example from this tutorial (we will see later) uses this to build a
three-column system for the website, separating content from layout.
## Combining pages
Now you know that `Page`s, and `Renderable`s in general, are basically nothing
more than key-values mappings, it is time to abuse this fact. There is another
`Renderable` type we haven't talked about before: a `CombinedRenderable`.
The type signature of the `combine` function does a pretty good job at
explaining it:
~~~~~{.haskell}
main = hakyll $ do
directory css "css"
combine :: (Renderable a, Renderable b)
=> a -> b -> CombinedRenderable a b
~~~~~
## Finding the posts, and a bit about renderables
This means we can take two `Renderable` values and combine them. This is
basically a `Map.union`: The result will contain all keys from `a`, and all
keys from `b`. If a key is present in both `Renderable`s, the value from `a`
will be chosen. This is, for example, always the case with an `url` (since
all `Renderable` types always have an url).
`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
provide us with all the blog posts. The blog posts have a
`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
can sort them easily, you could of course name them whatever you want. They
contain some metadata, too:
> title: A first post
> author: Julius Caesar
> date: November 5, 2009
> ---
> Lorem ipsum dolor sit amet, consectetur adipiscing elit.
> Vivamus pretium leo adipiscing lectus iaculis lobortis.
> Vivamus scelerisque velit dignissim metus...
Now, we find the posts and sort them reversed:
Combining two `Renderable`s, but setting a different `url` is quite common, so
there is another function that helps us here:
~~~~~{.haskell}
-- Find all post paths.
postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
combineWithURL :: (Renderable a, Renderable b)
=> FilePath -> a -> b -> CombinedRenderable a b
~~~~~
Our `postPaths` value is now of the type `[FilePath]`. `FilePath` is no
instance of `Renderable`, but `PagePath` is:
## The example
~~~~~{.haskell}
let renderablePosts = map createPagePath postPaths
~~~~~
Now that we have the tools, we'll get on to the example. This time, we'll
be making a more advanced brochure site. Here
[is a zip file](examples/morepages.zip) containing the source code for the
tutorial.
We have two templates we want to render our posts with: first we would like to
render them using `templates/post.html`, and we want to render the result
using `templates/default.html`. This can be done with the `renderChain`
function:
Every page consists of three sections, originally named `section1`, `section2`
and `section3`. So our pages look more or less like this:
~~~~~{.haskell}
mapM_ (renderChain [ "templates/post.html"
, "templates/default.html"
]) renderablePosts
~~~~~
---
title: About
Remember that the `renderChain` works by rendering the datatype using the first
template, creating a new page with the render result in the `body` field, and so
on until it has been rendered with all templates.
--- section1
## Mattis
Nullam imperdiet sodales orci vitae molestie. Nunc...
Now, we have the posts rendered. What is left is to generate some kind of index
page with links to those posts. We want one general list showing all posts, and
we want to show a few recent posts on the index page.
--- section2
## Orci
Vivamus eget mauris sit amet nulla laoreet lobortis. Nulla in...
## Custom Pages
--- section3
## Augue
In urna ante, pulvinar et imperdiet nec, fermentum ac...
Currently, there are 3 renderable datatypes in Hakyll:
- `Page`: The result of any rendering action. It is generally not recommended
to use pages a lot, because they cannot check dependencies (and therefore,
you would always regenerate your entire site if you use pages the wrong way).
- `PagePath`: Basically just a `FilePath` in a box. Internally, this will use
a `Page` for rendering, but `PagePath` provides better dependency checking
and works on a higher level.
- `CustomPage`: Basically the name says it - the preferred way of creating
custom pages in Hakyll.
We will use a `CustomPage` here. Basically, all `Renderable` datatypes are in
the end just `key: value` mappings. A CustomPage is created using the
`createCustomPage` function, which has the following type signature:
~~~~~{.haskell}
createCustomPage :: FilePath
-> [FilePath]
-> [(String, Either String (IO String)]
~~~~~
The first argument is the `url` of the page to generate. For our index page,
this will be, `index.html`. The second argument is _a list of dependencies_.
Basically, you should here give a list of files on which your custom page
depends.
The last argument is obviously our `key: value` mapping. But why the `Either`?
This, once again, is about dependency handling. The idea is that you can choose
which type to use for the value:
- `String`: Simply a `String`.
- `IO String`: Here, you can give an arbitrary `IO` action that will result
in a String. However - this action _will not be executed_ when the file
in `_site` is up-to-date.
First, let us define this `IO String` for our index page. We want to render
every post using a simple template:
The cool thing is we do not have to specify how these will be layed out. In our
template, we decide to use a simple three column system:
~~~~~{.html}
<li>
<a href="$$root/$url">$title</a>
- <em>$date</em> - by <em>$author</em>
</li>
<div class="column"> $section1 </div>
<div class="column"> $section2 </div>
<div class="column"> $section3 </div>
~~~~~
When every post is rendered with this template, we then want to concatenate the
result. Since rendering and concatenating is pretty common, Hakyll provides us
with a high-level function to do this.
The columns are then floated using css. So far so good, but what if we wanted
an additional text block on every page? An easy solution would be to add this
to the template, but then our layout-content separation idea will be broken
again. So we simply add to the template:
~~~~~{.html}
<div class="footer"> $footer </div>
~~~~~
And now we will use `combine` to put the footer on every page. We write a small
auxiliary function that combines a given `Renderable` with the footer:
~~~~~{.haskell}
let recentPosts = renderAndConcat "templates/postitem.html"
(take 3 renderablePosts)
withFooter a = a `combine` createPagePath "footer.markdown"
~~~~~
Now, creating our custom page is fairly straight-forward:
Now, were we previously wrote:
~~~~~{.haskell}
createCustomPage "index.html"
("templates/postitem.html" : take 3 postPaths)
[ ("title", Left "All posts")
, ("posts", Right recentPosts)
]
render "about.markdown"
where render = renderChain ["templates/default.html"]
. createPagePath
~~~~~
You can see our three arguments here. We're rendering `index.html`, then we tell
Hakyll on what files it depends - here the `templates/postitem.html` template
and the latest 3 posts. Finally, we give a `title` value to substitute in the
template, and the result of our concatenation. Of course, we also need to render
this custom page:
We simply have to add our footer:
~~~~~{.haskell}
renderChain ["index.html", "templates/default.html"] $
createCustomPage "index.html"
("templates/postitem.html" : take 3 postPaths)
[ ("title", Left "All posts")
, ("posts", Right recentPosts)
]
render "about.markdown"
where render = renderChain ["templates/default.html"]
. withFooter
. createPagePath
~~~~~
Note that the `index.html` in the `renderChain` list is also a template.
And now every page will include the footer.
## That's that
## That's all folks
If you have any more questions, feel free to ask them on the
I hope this tutorial was clear enough to teach you the concepts of pages and
combining renderables. As always, questions and feedback are welcome at the
[google discussion group](http://groups.google.com/group/hakyll).
There is a [next tutorial](tutorial3.html), explaining how to add an RSS feed
to our sample blog.

View file

@ -1,124 +1,162 @@
---
title: Tutorial (Part III)
what: adds an RSS feed to the blog from the previous tutorial
what: creates a simple blog
---
## Adding RSS to our simple blog
## Creating a simple blog with Hakyll
In this tutorial, we're going to add an RSS feed to the blog we wrote in
[the previous tutorial](tutorial2.html). Here is a
[zip file containing the source](examples/rssblog.zip).
After we created a simple brochure site, we're going to try something more
advanced: we are going to create a simple blog system.
An RSS feed looks like this:
A [zip file containing the source](examples/simpleblog.zip) for this
tutorial is also available.
~~~~~{.xml}
<?xml version="1.0" ?>
<rss version="2.0">
<channel>
<title>The SimpleBlog</title>
<link>http://example.com/</link>
<description>Simple blog in hakyll</description>
<item>
<title>Title goes here</title>
<link>http://example.com/post.html</link>
<description>
A description is optional.
</description>
</item>
</channel>
</rss>
~~~~~
Note that, obviously, there can be more than one item. We're going to use a
template to render this. This is where `templates/rss.xml` comes in:
~~~~~{.xml}
<?xml version="1.0" ?>
<rss version="2.0">
<channel>
<title>The SimpleBlog</title>
<link>http://jaspervdj.be/</link>
<description>Simple blog in hakyll</description>
$items
</channel>
</rss>
~~~~~
We thus render our feed with the following code (no, I didn't define `rssPage`
yet - I'm going to work from bottom to top here, it's easier to explain it
that way).
Blogs, as you probably know, are composed of posts. In Hakyll, we're going
to use simple pages for posts. All posts are located in the `posts`
directory. But we're not going to use the `directory` command here - you will
see why later. First, some trivial things like css.
~~~~~{.haskell}
renderChain ["templates/rss.xml"] rssPage
main = hakyll $ do
directory css "css"
~~~~~
This, as you can see, is a regular render chain, once again. We need make a
`Renderable` that "fills in" the `$items` identifier. We're going to do this
using a [custom page](tutorial2.html#custom-pages).
## Finding the posts, and a bit about renderables
## Custom pages again
`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
provide us with all the blog posts. The blog posts have a
`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
can sort them easily, you could of course name them whatever you want. They
contain some metadata, too:
Note that we do not have to include all posts in the rss feed - only a few
recent ones. We'll settle on the latest three here.
title: A first post
author: Julius Caesar
date: November 5, 2009
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Vivamus pretium leo adipiscing lectus iaculis lobortis.
Vivamus scelerisque velit dignissim metus...
We want to render every post using the following template,
`templates/rssitem.xml`:
Now, we find the posts and sort them reversed:
~~~~~{.haskell}
<item>
<title>$title</title>
<link>http://example.com/$url</link>
<description>$title by $author</description>
</item>
-- Find all post paths.
postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
~~~~~
Since we build on the previous example, we still have our `renderablePosts`
list. We'll be using it again:
Our `postPaths` value is now of the type `[FilePath]`. `FilePath` is no
instance of `Renderable`, but `PagePath` is:
~~~~~{.haskell}
let recentRSSItems = renderAndConcat "templates/rssitem.xml"
let renderablePosts = map createPagePath postPaths
~~~~~
We have two templates we want to render our posts with: first we would like to
render them using `templates/post.html`, and we want to render the result
using `templates/default.html`. This can be done with the `renderChain`
function:
~~~~~{.haskell}
mapM_ (renderChain [ "templates/post.html"
, "templates/default.html"
]) renderablePosts
~~~~~
Remember that the `renderChain` works by rendering the datatype using the first
template, creating a new page with the render result in the `body` field, and so
on until it has been rendered with all templates.
Now, we have the posts rendered. What is left is to generate some kind of index
page with links to those posts. We want one general list showing all posts, and
we want to show a few recent posts on the index page.
## Custom Pages
Currently, there are 3 renderable datatypes in Hakyll:
- `Page`: The result of any rendering action. It is generally not recommended
to use pages a lot, because they cannot check dependencies (and therefore,
you would always regenerate your entire site if you use pages the wrong way).
- `PagePath`: Basically just a `FilePath` in a box. Internally, this will use
a `Page` for rendering, but `PagePath` provides better dependency checking
and works on a higher level.
- `CustomPage`: Basically the name says it - the preferred way of creating
custom pages in Hakyll.
We will use a `CustomPage` here. Basically, all `Renderable` datatypes are in
the end just `key: value` mappings. A CustomPage is created using the
`createCustomPage` function, which has the following type signature:
~~~~~{.haskell}
createCustomPage :: FilePath
-> [FilePath]
-> [(String, Either String (IO String)]
~~~~~
The first argument is the `url` of the page to generate. For our index page,
this will be, `index.html`. The second argument is _a list of dependencies_.
Basically, you should here give a list of files on which your custom page
depends.
The last argument is obviously our `key: value` mapping. But why the `Either`?
This, once again, is about dependency handling. The idea is that you can choose
which type to use for the value:
- `String`: Simply a `String`.
- `IO String`: Here, you can give an arbitrary `IO` action that will result
in a String. However - this action _will not be executed_ when the file
in `_site` is up-to-date.
First, let us define this `IO String` for our index page. We want to render
every post using a simple template:
~~~~~{.html}
<li>
<a href="$$root/$url">$title</a>
- <em>$date</em> - by <em>$author</em>
</li>
~~~~~
When every post is rendered with this template, we then want to concatenate the
result. Since rendering and concatenating is pretty common, Hakyll provides us
with a high-level function to do this.
~~~~~{.haskell}
let recentPosts = renderAndConcat ["templates/postitem.html"]
(take 3 renderablePosts)
~~~~~
We're using the `renderAndConcat` function again. Note that because of
hakyll/haskell laziness, this action isn't executed directly, and this helps
dependency handling.
Now, the `rssPage` page. As you might remember, we use the `createCustomPage`
function to create a custom page. We first give the destination url, then a
list of dependencies, and then a list of `(key, value)` pairs.
Now, creating our custom page is fairly straight-forward:
~~~~~{.haskell}
let rssPage = createCustomPage
"rss.xml"
createCustomPage "index.html"
("templates/postitem.html" : take 3 postPaths)
[("items", Right recentRSSItems)]
[ ("title", Left "All posts")
, ("posts", Right recentPosts)
]
~~~~~
## Adding a link to the feed
You can see our three arguments here. We're rendering `index.html`, then we tell
Hakyll on what files it depends - here the `templates/postitem.html` template
and the latest 3 posts. Finally, we give a `title` value to substitute in the
template, and the result of our concatenation. Of course, we also need to render
this custom page:
According to the w3 organization,
[you should add \<link\> tags](http://www.w3.org/QA/Tips/use-links) to your
documents, so we'll do this. In `templates/default.html`:
~~~~~{.haskell}
renderChain ["index.html", "templates/default.html"] $
createCustomPage "index.html"
("templates/postitem.html" : take 3 postPaths)
[ ("title", Left "All posts")
, ("posts", Right recentPosts)
]
~~~~~
~~~~~~{.html}
<head>
<title>SimpleBlog - $title</title>
<link rel="stylesheet" type="text/css" href="$$root/css/default.css" />
<link rel="alternate"
type="application/rss+xml"
title="SimpleBlog"
href="http://example.com/rss.xml" />
</head>
~~~~~~
Note that the `index.html` in the `renderChain` list is also a template.
This makes most browsers see the rss feed, and show it in the address bar.
If you want, you can also add a pretty button to your blog linking to
`rss.xml`.
## That's that
## That's it!
Yep, that's it. Feel free to play with the source code in
[the zip file](examples/rssblog.zip) and extend the blog further. As always,
all questions are welcome on the
If you have any more questions, feel free to ask them on the
[google discussion group](http://groups.google.com/group/hakyll).
There is a [next tutorial](tutorial4.html), explaining how to add an RSS feed
to our sample blog.

View file

@ -1,211 +1,124 @@
---
title: Tutorial (Part IV)
what: adds tags and context manipulations to our blog
what: adds an RSS feed to the blog from the previous tutorial
---
Here, have [a zip file](examples/tagblog.zip) for this tutorial.
## Adding RSS to our simple blog
## Context manipulations
In this tutorial, we're going to add an RSS feed to the blog we wrote in
[the previous tutorial](tutorial2.html). Here is a
[zip file containing the source](examples/rssblog.zip).
As you might remember, `Renderable` objects are usually just key-value mappings.
We can render those with templates, and then the `$key`'s in the template get
substituted by the appropriate values. This is a rather flexible system, but
there are limitations. Some of these limitations can be solved using
_Context Manipulations_.
An RSS feed looks like this:
In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
~~~~~{.haskell}
type ContextManipulation = Context -> Context
~~~~~{.xml}
<?xml version="1.0" ?>
<rss version="2.0">
<channel>
<title>The SimpleBlog</title>
<link>http://example.com/</link>
<description>Simple blog in hakyll</description>
<item>
<title>Title goes here</title>
<link>http://example.com/post.html</link>
<description>
A description is optional.
</description>
</item>
</channel>
</rss>
~~~~~
Where `Context` is a key-value mapping. Usually, you don't create
`ContextManipulation`'s by hand, but you use the `renderValue` function. Let's
have a look at it's type.
Note that, obviously, there can be more than one item. We're going to use a
template to render this. This is where `templates/rss.xml` comes in:
~~~~~{.haskell}
renderValue :: String -> String
-> (String -> String)
-> ContextManipulation
~~~~~{.xml}
<?xml version="1.0" ?>
<rss version="2.0">
<channel>
<title>The SimpleBlog</title>
<link>http://jaspervdj.be/</link>
<description>Simple blog in hakyll</description>
$items
</channel>
</rss>
~~~~~
This is the preferred way of creating context manipulations. The first argument
is the `key` to manipulate. The second argument is the `key` where the new value
should be placed. If this is the same as the first argument, it will be
replaced. The third argument is the function to manipulate the `value` with.
As a simple example, let's write a function that puts the `$title` in uppercase.
We thus render our feed with the following code (no, I didn't define `rssPage`
yet - I'm going to work from bottom to top here, it's easier to explain it
that way).
~~~~~{.haskell}
import Data.Char (toUpper)
titleUpper :: ContextManipulation
titleUpper = renderValue "title" "title" $ map toUpper
renderChain ["templates/rss.xml"] rssPage
~~~~~
## Applying Context Manipulations
This, as you can see, is a regular render chain, once again. We need make a
`Renderable` that "fills in" the `$items` identifier. We're going to do this
using a [custom page](tutorial2.html#custom-pages).
Now, the question is how to apply these `ContextManipulation`'s. The answer is
simple. For every important render function (`render`, `renderChain`,
`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
first argument. These functions are thus: `renderWith`, `renderChainWith`,
`renderAndConcatWith`. In fact, the following holds true:
## Custom pages again
Note that we do not have to include all posts in the rss feed - only a few
recent ones. We'll settle on the latest three here.
We want to render every post using the following template,
`templates/rssitem.xml`:
~~~~~{.haskell}
render == renderWith id
renderChain == renderChainWith id
renderAndConcat == renderAndConcatWith id
<item>
<title>$title</title>
<link>http://example.com/$url</link>
<description>$title by $author</description>
</item>
~~~~~
So we could use or title manipulation like this:
Since we build on the previous example, we still have our `renderablePosts`
list. We'll be using it again:
~~~~~{.haskell}
renderChainWith titleUpper ["templates/default.html"]
(createPagePath "index.markdown")
let recentRSSItems = renderAndConcat ["templates/rssitem.xml"]
(take 3 renderablePosts)
~~~~~
## Rendering dates
We're using the `renderAndConcat` function again. Note that because of
hakyll/haskell laziness, this action isn't executed directly, and this helps
dependency handling.
As you remember, in our previous blog, all posts had a file name like
`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
had a metadata field `date`, containing a human-readable date. This is not very
D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
dates encoded in paths: `renderDate`.
Now, the `rssPage` page. As you might remember, we use the `createCustomPage`
function to create a custom page. We first give the destination url, then a
list of dependencies, and then a list of `(key, value)` pairs.
~~~~~{.haskell}
postManipulation :: ContextManipulation
postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
~~~~~
That manipulation will:
- Read the date from the file name the post was loaded from.
- Parse the date and render it in a `%B %e, %Y` format. This is a
`Month day, Year` format.
- Put the result in the `date` metadata field.
- If the date could not be parsed, it will put `"Unknown date"` in the `date`
metadata field.
So, we can throw away our `date: ` lines from our posts, and still use `$date`
in our templates.
## Abstracting the post list
Now, we're going to render tags. This is also done using context manipulations.
Hakyll has a specialized module to deal with tags, provided by
`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
the `tags` metadata field.
> ---
> title: A third post
> author: Publius Ovidius Naso
> tags: epic fail, ovidius
> ---
> Pellentesque tempor blandit elit, vel...
But first things first. We need to render a post list for every tag. We already
had some code to render a list of all posts. We're just going to abstract this
code into a more general function:
~~~~{.haskell}
renderPostList url title posts = do
let postItems = renderAndConcatWith
postManipulation
"templates/postitem.html"
(map createPagePath posts)
customPage = createCustomPage
url
("templates/postitem.html" : posts)
[ ("title", Left title)
, ("posts", Right postItems)
]
renderChain ["posts.html", "templates/default.html"]
customPage
~~~~~
Our "render all posts" action can now be written as:
~~~~~{.haskell}
renderPostList "posts.html" "All posts" postPaths
~~~~~
## Tag links
We want to display the tags for our post under the title. But if we use the
`$tags` key in a template, we will just have the plain tags - they will not be
clickable. We can again solve this with a `ContextManipulation`. We have a
function that produces an url for a given tag:
~~~~~{.haskell}
tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
~~~~~
`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
a specialized `renderValue` function for creating linked tags called
`renderTagLinks`. This function simply takes a function that produces an url
for a given tag - the function we just wrote. Let's extend our
`postManipulation`.
~~~~~{.haskell}
postManipulation :: ContextManipulation
postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
. renderTagLinks tagToURL
~~~~~
If we click a tag, we get a `404`. That's because we haven't generated the
post lists for every tag.
## The Tag Map
Hakyll provides a function called `readTagMap`. Let's inspect it's type.
~~~~~{.haskell}
readTagMap [FilePath] -> IO Map String [FilePath]
~~~~~
You give it a list of paths, and it creates a map that, for every tag, holds
a number of posts. We can easily use this to render a post list for every tag.
~~~~~{.haskell}
tagMap <- readTagMap postPaths
let renderListForTag (tag, posts) =
renderPostList (tagToURL tag)
("Posts tagged " ++ tag)
mapM_ renderListForTag (toList tagMap)
~~~~~
There we go. We now have clickable tags, and a post list for every tag.
## A Tag Cloud
A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
generate a tag cloud. Let's have a look at the `renderTagCloud` function.
~~~~~{.haskell}
TagCloud :: M.Map String [FilePath]
-> (String -> String)
-> Float
-> Float
-> String
~~~~~
The first argument is obviously the result of the `readTagMap` function. The
second argument is, once again, a function to create an url for a given tag.
Then, we give a minimum and a maximum font size in percent, and we get the
tag cloud back. We can add this to our index:
~~~~~{.haskell}
let tagCloud = renderTagCloud tagMap tagToURL 100 200
...
createCustomPage "index.html"
let rssPage = createCustomPage
"rss.xml"
("templates/postitem.html" : take 3 postPaths)
[ ("title", Left "Home")
, ("posts", Right recentPosts)
, ("tagcloud", Left tagCloud)
]
[("items", Right recentRSSItems)]
~~~~~
## That's it
## Adding a link to the feed
Feel free to hack around with the code from the zip file. As always, if you
still have questions, ask them at the
According to the w3 organization,
[you should add \<link\> tags](http://www.w3.org/QA/Tips/use-links) to your
documents, so we'll do this. In `templates/default.html`:
~~~~~~{.html}
<head>
<title>SimpleBlog - $title</title>
<link rel="stylesheet" type="text/css" href="$$root/css/default.css" />
<link rel="alternate"
type="application/rss+xml"
title="SimpleBlog"
href="http://example.com/rss.xml" />
</head>
~~~~~~
This makes most browsers see the rss feed, and show it in the address bar.
If you want, you can also add a pretty button to your blog linking to
`rss.xml`.
## That's it!
Yep, that's it. Feel free to play with the source code in
[the zip file](examples/rssblog.zip) and extend the blog further. As always,
all questions are welcome on the
[google discussion group](http://groups.google.com/group/hakyll).

View file

@ -0,0 +1,211 @@
---
title: Tutorial (Part V)
what: adds tags and context manipulations to our blog
---
Here, have [a zip file](examples/tagblog.zip) for this tutorial.
## Context manipulations
As you might remember, `Renderable` objects are usually just key-value mappings.
We can render those with templates, and then the `$key`'s in the template get
substituted by the appropriate values. This is a rather flexible system, but
there are limitations. Some of these limitations can be solved using
_Context Manipulations_.
In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
~~~~~{.haskell}
type ContextManipulation = Context -> Context
~~~~~
Where `Context` is a key-value mapping. Usually, you don't create
`ContextManipulation`'s by hand, but you use the `renderValue` function. Let's
have a look at it's type.
~~~~~{.haskell}
renderValue :: String -> String
-> (String -> String)
-> ContextManipulation
~~~~~
This is the preferred way of creating context manipulations. The first argument
is the `key` to manipulate. The second argument is the `key` where the new value
should be placed. If this is the same as the first argument, it will be
replaced. The third argument is the function to manipulate the `value` with.
As a simple example, let's write a function that puts the `$title` in uppercase.
~~~~~{.haskell}
import Data.Char (toUpper)
titleUpper :: ContextManipulation
titleUpper = renderValue "title" "title" $ map toUpper
~~~~~
## Applying Context Manipulations
Now, the question is how to apply these `ContextManipulation`'s. The answer is
simple. For every important render function (`render`, `renderChain`,
`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
first argument. These functions are thus: `renderWith`, `renderChainWith`,
`renderAndConcatWith`. In fact, the following holds true:
~~~~~{.haskell}
render == renderWith id
renderChain == renderChainWith id
renderAndConcat == renderAndConcatWith id
~~~~~
So we could use or title manipulation like this:
~~~~~{.haskell}
renderChainWith titleUpper ["templates/default.html"]
(createPagePath "index.markdown")
~~~~~
## Rendering dates
As you remember, in our previous blog, all posts had a file name like
`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
had a metadata field `date`, containing a human-readable date. This is not very
D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
dates encoded in paths: `renderDate`.
~~~~~{.haskell}
postManipulation :: ContextManipulation
postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
~~~~~
That manipulation will:
- Read the date from the file name the post was loaded from.
- Parse the date and render it in a `%B %e, %Y` format. This is a
`Month day, Year` format.
- Put the result in the `date` metadata field.
- If the date could not be parsed, it will put `"Unknown date"` in the `date`
metadata field.
So, we can throw away our `date: ` lines from our posts, and still use `$date`
in our templates.
## Abstracting the post list
Now, we're going to render tags. This is also done using context manipulations.
Hakyll has a specialized module to deal with tags, provided by
`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
the `tags` metadata field.
---
title: A third post
author: Publius Ovidius Naso
tags: epic fail, ovidius
---
Pellentesque tempor blandit elit, vel...
But first things first. We need to render a post list for every tag. We already
had some code to render a list of all posts. We're just going to abstract this
code into a more general function:
~~~~{.haskell}
renderPostList url title posts = do
let postItems = renderAndConcatWith
postManipulation
"templates/postitem.html"
(map createPagePath posts)
customPage = createCustomPage
url
("templates/postitem.html" : posts)
[ ("title", Left title)
, ("posts", Right postItems)
]
renderChain ["posts.html", "templates/default.html"]
customPage
~~~~~
Our "render all posts" action can now be written as:
~~~~~{.haskell}
renderPostList "posts.html" "All posts" postPaths
~~~~~
## Tag links
We want to display the tags for our post under the title. But if we use the
`$tags` key in a template, we will just have the plain tags - they will not be
clickable. We can again solve this with a `ContextManipulation`. We have a
function that produces an url for a given tag:
~~~~~{.haskell}
tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
~~~~~
`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
a specialized `renderValue` function for creating linked tags called
`renderTagLinks`. This function simply takes a function that produces an url
for a given tag - the function we just wrote. Let's extend our
`postManipulation`.
~~~~~{.haskell}
postManipulation :: ContextManipulation
postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
. renderTagLinks tagToURL
~~~~~
If we click a tag, we get a `404`. That's because we haven't generated the
post lists for every tag.
## The Tag Map
Hakyll provides a function called `readTagMap`. Let's inspect it's type.
~~~~~{.haskell}
readTagMap [FilePath] -> IO Map String [FilePath]
~~~~~
You give it a list of paths, and it creates a map that, for every tag, holds
a number of posts. We can easily use this to render a post list for every tag.
~~~~~{.haskell}
tagMap <- readTagMap postPaths
let renderListForTag (tag, posts) =
renderPostList (tagToURL tag)
("Posts tagged " ++ tag)
mapM_ renderListForTag (toList tagMap)
~~~~~
There we go. We now have clickable tags, and a post list for every tag.
## A Tag Cloud
A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
generate a tag cloud. Let's have a look at the `renderTagCloud` function.
~~~~~{.haskell}
TagCloud :: M.Map String [FilePath]
-> (String -> String)
-> Float
-> Float
-> String
~~~~~
The first argument is obviously the result of the `readTagMap` function. The
second argument is, once again, a function to create an url for a given tag.
Then, we give a minimum and a maximum font size in percent, and we get the
tag cloud back. We can add this to our index:
~~~~~{.haskell}
let tagCloud = renderTagCloud tagMap tagToURL 100 200
...
createCustomPage "index.html"
("templates/postitem.html" : take 3 postPaths)
[ ("title", Left "Home")
, ("posts", Right recentPosts)
, ("tagcloud", Left tagCloud)
]
~~~~~
## That's it
Feel free to hack around with the code from the zip file. As always, if you
still have questions, ask them at the
[google discussion group](http://groups.google.com/group/hakyll).

View file

@ -0,0 +1,27 @@
---
title: About
--- section1
## Mattis
Nullam imperdiet sodales orci vitae molestie. Nunc quam orci, pharetra a
rhoncus vitae, eleifend id felis. Suspendisse potenti. Etiam vitae urna orci.
Quisque pellentesque dignissim felis, egestas tempus urna luctus vitae. In hac
habitasse platea dictumst. Morbi fringilla mattis odio, et mattis tellus
accumsan vitae.
--- section2
## Orci
Vivamus eget mauris sit amet nulla laoreet lobortis. Nulla in diam elementum
risus convallis commodo. Cras vehicula varius dui vitae facilisis. Proin
elementum libero eget leo aliquet quis euismod orci vestibulum. Duis rhoncus
lorem consequat tellus vestibulum aliquam. Quisque orci orci, malesuada porta
blandit et, interdum nec magna.
--- section3
## Augue
In urna ante, pulvinar et imperdiet nec, fermentum ac tortor. Cras tristique
pellentesque euismod. Pellentesque est ante, sagittis vitae vehicula vitae,
ullamcorper eget lectus. Curabitur egestas accumsan leo, ac ullamcorper nibh
tincidunt id. Curabitur lorem libero, fermentum non tincidunt ac, pretium in
libero. Donec vel mi eu tortor accumsan dictum ut in augue. Vestibulum est
lorem, bibendum eu vehicula eu, convallis eget mauris.

View file

@ -0,0 +1,27 @@
body {
width: 600px;
margin: 0px auto 0px auto;
}
div#navigation {
text-align: center;
border-bottom: 4px solid black;
}
div#navigation a {
color: white;
text-decoration: none;
background-color: black;
padding: 3px 10px 3px 10px;
margin: 0px 10px 0px 10px;
}
div.column {
width: 30%;
float: left;
margin: 0px 10px 0px 10px;
}
div#footer {
clear: both;
}

View file

@ -0,0 +1,16 @@
--- footer
## Sapien
In hac habitasse platea dictumst. Cras placerat felis nec risus varius in
accumsan sem fermentum. Vestibulum elementum aliquam tortor semper vulputate.
Vivamus tincidunt tellus sed purus tempor fringilla. Morbi dui nisl, eleifend
non dictum vitae, luctus eu lacus. Duis vitae lacus sem, ut porta mauris.
Aenean sed ultricies dui. Vivamus ullamcorper metus lorem, at ornare nibh.
Mauris mi metus, convallis id lobortis vitae, interdum quis felis. Cras elit
massa, pellentesque sit amet pharetra ut, volutpat in arcu. Vivamus blandit,
ligula et ultricies consequat, metus sem congue quam, ac pretium enim velit at
tortor. Cras in tellus eu sapien pulvinar sollicitudin eu id ipsum. Mauris nec
urna tellus, et scelerisque tellus. Nunc imperdiet felis nec libero consectetur
tristique tristique ipsum sodales. Cras tortor nisl, condimentum in
pellentesque id, interdum vel mi. Suspendisse auctor vehicula orci at
scelerisque. Vivamus quis sagittis felis.

View file

@ -0,0 +1,13 @@
import Text.Hakyll (hakyll, defaultHakyllConfiguration)
import Text.Hakyll.File (directory)
import Text.Hakyll.Render (css, static, renderChain)
import Text.Hakyll.Renderables (createPagePath, combine)
main = hakyll defaultHakyllConfiguration $ do
directory css "css"
render "about.markdown"
render "index.markdown"
render "products.markdown"
where
render = renderChain ["templates/default.html"] . withFooter . createPagePath
withFooter a = a `combine` createPagePath "footer.markdown"

View file

@ -0,0 +1,24 @@
---
title: Home
--- section1
## Purus
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempor, urna et
auctor tincidunt, eros mauris facilisis purus, eget sollicitudin leo massa sit
amet ipsum. Vivamus eu massa in urna vehicula rutrum eget sit amet purus.
--- section2
## Ligula
Mauris sit amet justo mi. Curabitur vel quam felis. In hac habitasse platea
dictumst. Etiam nec consequat risus. Donec consequat est vitae neque fermentum
feugiat nec ac nibh. Nulla rhoncus, odio quis scelerisque rutrum, metus sem
tempor ante, a ornare ipsum felis sed ligula. Morbi urna lectus, scelerisque
non pharetra in, rutrum quis ligula. Phasellus semper ullamcorper arcu eu
auctor.
--- section3
## Justo
Aliquam sagittis tincidunt libero ut elementum. Ut sit amet vestibulum metus.
Ut aliquet congue neque eu tincidunt. Integer eu elit sed massa sollicitudin
vehicula nec ut sem. Cras euismod enim eget purus lacinia non feugiat urna
imperdiet. Aliquam justo sem, viverra eu vehicula vitae, imperdiet vel magna.

View file

@ -0,0 +1,23 @@
---
title: Products
--- section1
## Lacus
Etiam condimentum auctor semper. Donec lobortis, magna id sodales sollicitudin,
lectus mi egestas nulla, pulvinar lobortis nunc eros id nisl. Curabitur
imperdiet, erat at accumsan vulputate, purus nunc blandit nulla, dictum
vestibulum sem lorem eget ipsum.
--- section2
## Vitae
Integer ut dui eu felis mollis vestibulum. Etiam at nibh id diam aliquet
vestibulum sit amet a nibh. Aliquam erat volutpat. Etiam vitae nulla at dolor
fringilla tempor ut a nunc. Pellentesque elementum elit lorem. Quisque nec
ligula ipsum. Nunc augue lacus, ullamcorper vel dapibus in, mattis eget elit.
--- section3
## Feugiat
Pellentesque enim dui, interdum elementum vehicula luctus, feugiat vitae arcu.
Vestibulum ut felis justo. Quisque vestibulum mauris eget ipsum luctus
consequat. Nunc tincidunt, turpis ut fermentum dapibus,
justo tortor bibendum sem, at facilisis justo odio luctus lectus.

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>MyAweSomeCompany - $title</title>
<link rel="stylesheet" type="text/css" href="$root/css/default.css" />
</head>
<body>
<h1>MyAweSomeCompany - $title</h1>
<div id="navigation">
<a href="$root/index.html">Home</a>
<a href="$root/about.html">About</a>
<a href="$root/products.html">Products</a>
</div>
<div class="column"> $section1 </div>
<div class="column"> $section2 </div>
<div class="column"> $section3 </div>
<div id="footer"> $footer </div>
</body>
</html>