Add tutorial about templates

This commit is contained in:
Jasper Van der Jeugt 2010-08-06 13:28:23 +02:00
parent d0a9d01062
commit d3b87ba259
7 changed files with 598 additions and 555 deletions

View file

@ -1,6 +1,6 @@
---
title: How to write pages
what: elaborates a little on writing pages and templates
what: elaborates a little on writing pages
---
## The structure of a Page

View file

@ -1,157 +1,60 @@
---
title: Creating a Blog
what: creates a simple blog
title: How to write templates
what: more information on template writing
---
## Creating a simple blog with Hakyll
## Simple templates
After we created a simple brochure site, we're going to try something more
advanced: we are going to create a simple blog system.
A [zip file] containing the source for this tutorial is also available.
[zip file]: $root/examples/simpleblog.zip
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}
main = hakyll "http://example.com" $ do
directory css "css"
~~~~~
## Finding the posts
`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 (sorting on filename implies sorting on date). You could of
course name them whatever you want, but it's always a good idea to stick to the
conventions. 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, so the most recent post will
become the first item in the list:
~~~~~{.haskell}
postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
~~~~~
Our `postPaths` value is now of the type `[FilePath]`. We want to be able to
render all posts, so we pass them to the `createPage` function.
~~~~~{.haskell}
let postPages = map createPage 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"
]) postPages
~~~~~
Remember that the `renderChain` works by rendering the item 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.
## Creating listings.
`createPage` is the easiest way of reading a `Context`. But in this case, we
want something more custom, so we'll use the `createCustomPage` function. This
allows us to create a more specific `Context`.
~~~~~{.haskell}
createCustomPage :: FilePath
-> [(String, Either String (HakyllAction () String)]
-> HakyllAction () Context
~~~~~
The first argument is the `url` of the page to generate. For our index page,
this will be, `index.html`. The second 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`.
- `HakyllAction () String`: Here, you can give an `HakyllAction` Arrow action
that can produce a String. However - this action _will not be executed_ when
the file in `_site` is up-to-date.
However, in this specific case - a list of posts - there is an easier, and more
high-level approach than `createCustomPage`[^1]. Let's look at the type
signature of `createListing`:
~~~~~{.haskell}
createListing :: FilePath
-> [FilePath]
-> [HakyllAction () Context]
-> [(String, Either String (HakyllAction () String)]
-> HakyllAction () Context
~~~~~
[^1]: Since Hakyll-1.3 onwards.
The first argument is the destination url. For our blog, this is of course
`index.html`. The second argument is a list templates to render _each_ `Context`
with. We use only `templates/postitem.html` here. This is, as you can see, a
simple template:
Simple templates are simply HTML files, with `$identifiers`. An example:
~~~~~{.html}
<li>
<a href="$$root/$url">$title</a>
- <em>$date</em> - by <em>$author</em>
</li>
<html>
<head>
<title>$title</title>
</head>
<body>
$body
</body>
</html>
~~~~~
We then give a list of @Context@s to render. For our index, these are the 3 last
posts. The last argument of the `createListing` functions lets you specify
additional key-value pairs, like in `createCustomPage`. We use this to set the
title of our page. So, we create our index page using:
## Markup in templates
~~~~~{.haskell}
let index = createListing "index.html"
["templates/postitem.html"]
(take 3 postPages)
[("title", Left "Home")]
~~~~~
Most of the examples in these tutorials use HTML for templates. However, since
Hakyll 2.2, it is possible use other markup languages in your templates. Simply
use an appropriate extension, and Hakyll will pick it up. For example, you could
write your `templates/post.markdown` template as:
The result of this will be a `HakyllAction () Context`. This `Context`'s `$body`
will contain a concatenation of all the 3 posts, rendered with the
`templates/postitem.html` template.
# $title
Now, we only have to render it: first using the `index.html` template - which
adds some more information to our index - then using the
`templates/default.html` template.
_On $date_
~~~~~{.haskell}
renderChain ["index.html", "templates/default.html"] index
~~~~~
$body
Note that the `index.html` in the `renderChain` list is also a template. Now,
you might want to take your time to read the `index.html` template and the other
files in the zip so you understand what is going on here.
__Warning__: you shouldn't use markdown for your "root" template, as these
templates will never insert things like the doctype for you -- so you always
need at least one top-level HTML template.
## The gist of it
## Hamlet templates
- You can find blogposts using `getRecursiveContents`.
- The convention is to call them `yyyy-mm-dd-rest-of-title.extension`. This
allows us to sort them easily.
- You can use `createCustomPage` or `createListing` to create custom pages and
simple listings.
From Hakyll 2.3 onwards, it is possible to use [hamlet] templates. You can find
more information about hamlet on that website. Usage is fairly simple -- since
pages are strictly key-value mappings, only `$variable$` control is supported in
hamlet templates. As an example, here is the template that can be used for the
brochure site, but in hamlet:
!!!
%html
%head
%title MyAweSomeCompany - $$title$
%body
%h1 MyAweSomeCompany - $$title$
#navigation
%a!href="$$root$/index.html" Home
%a!href="$$root$/about.html" About
%a!href="$$root$/code.html" Code
$body$
Hakyll will recognise hamlet templates automatically by the `.hamlet` extension.
[hamlet]: http://docs.yesodweb.com/hamlet/

View file

@ -1,84 +1,157 @@
---
title: Creating feeds
what: adds an rss feed to the simple blog
title: Creating a Blog
what: creates a simple blog
---
## Adding Feeds
## 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 part. Here is a [zip file] containing the source.
After we created a simple brochure site, we're going to try something more
advanced: we are going to create a simple blog system.
[zip file]: $root/examples/feedblog.zip
A [zip file] containing the source for this tutorial is also available.
You will be glad to hear that Hakyll has native support for RSS as well as Atom
feeds[^1]. This simplifies our object a lot.
[zip file]: $root/examples/simpleblog.zip
[^1]: Since Hakyll-2.0
This is the first time that the absolute URL of your site you have to give to
the `hakyll` function actually matters. That's because the specifications of
those feed formats dictate you need the full URL of them.
## Creating a configuration
The first thing to do is creating a configuration for your feed. You could
place this code outside of your main function, as is done in the example.
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}
myFeedConfiguration = FeedConfiguration
{ feedUrl = "rss.xml"
, feedTitle = "SimpleBlog RSS feed."
, feedDescription = "Simple demo of a feed created with Hakyll."
, feedAuthorName = "Jasper Van der Jeugt"
}
main = hakyll "http://example.com" $ do
directory css "css"
~~~~~
Note that we enter the url of the feed in our configuration. So the function
to render our feed only takes two arguments, the configuration and a list of
items to put in the feed. Let's put the three most recent posts in our feed.
## Finding the posts
`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 (sorting on filename implies sorting on date). You could of
course name them whatever you want, but it's always a good idea to stick to the
conventions. 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, so the most recent post will
become the first item in the list:
~~~~~{.haskell}
renderRss myFeedConfiguration (take 3 postPages)
postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
~~~~~
## But it's not that easy
If you look at our generated RSS feed (build the site), you will see
`$description` tags appearing in our final render. We don't want that! How
did they get there in the first place?
To render feeds, Hakyll expects a number of fields in the renderables you put
in the feed. They are:
- `$title`: Title of the item. This is set in our posts, since we use a `title`
metadata field.
- `$url`: Url of the item. This is automatically set by Hakyll, so you shouldn't
worry about it.
- `$description`: A description of our item to appear in the feed reader.
The latter is obviously the problem: we don't have a description in our posts.
In fact, we would like to copy the `$body` key to the `$description` key, so
people can read the full post in their feed readers.
## Where arrows come in
The `Text.Hakyll.ContextManipulations` module contains a number of simple
functions that create Arrows for us. One of these functions is `copyValue`,
which takes a source and a destination key. So, we need to pass our
items through this Arrow first.
Our `postPaths` value is now of the type `[FilePath]`. We want to be able to
render all posts, so we pass them to the `createPage` function.
~~~~~{.haskell}
renderRss myFeedConfiguration $
map (>>> copyValue "body" "description") (take 3 postPages)
let postPages = map createPage postPaths
~~~~~
And that's that, now our feed gets rendered properly. Exercise for the reader
is to add a Atom feed[^2].
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:
[^2]: Hint: look around in the [reference]($root/reference.html).
~~~~~{.haskell}
mapM_ (renderChain [ "templates/post.html"
, "templates/default.html"
]) postPages
~~~~~
Remember that the `renderChain` works by rendering the item 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.
## Creating listings.
`createPage` is the easiest way of reading a `Context`. But in this case, we
want something more custom, so we'll use the `createCustomPage` function. This
allows us to create a more specific `Context`.
~~~~~{.haskell}
createCustomPage :: FilePath
-> [(String, Either String (HakyllAction () String)]
-> HakyllAction () Context
~~~~~
The first argument is the `url` of the page to generate. For our index page,
this will be, `index.html`. The second 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`.
- `HakyllAction () String`: Here, you can give an `HakyllAction` Arrow action
that can produce a String. However - this action _will not be executed_ when
the file in `_site` is up-to-date.
However, in this specific case - a list of posts - there is an easier, and more
high-level approach than `createCustomPage`[^1]. Let's look at the type
signature of `createListing`:
~~~~~{.haskell}
createListing :: FilePath
-> [FilePath]
-> [HakyllAction () Context]
-> [(String, Either String (HakyllAction () String)]
-> HakyllAction () Context
~~~~~
[^1]: Since Hakyll-1.3 onwards.
The first argument is the destination url. For our blog, this is of course
`index.html`. The second argument is a list templates to render _each_ `Context`
with. We use only `templates/postitem.html` here. This is, as you can see, a
simple template:
~~~~~{.html}
<li>
<a href="$$root/$url">$title</a>
- <em>$date</em> - by <em>$author</em>
</li>
~~~~~
We then give a list of @Context@s to render. For our index, these are the 3 last
posts. The last argument of the `createListing` functions lets you specify
additional key-value pairs, like in `createCustomPage`. We use this to set the
title of our page. So, we create our index page using:
~~~~~{.haskell}
let index = createListing "index.html"
["templates/postitem.html"]
(take 3 postPages)
[("title", Left "Home")]
~~~~~
The result of this will be a `HakyllAction () Context`. This `Context`'s `$body`
will contain a concatenation of all the 3 posts, rendered with the
`templates/postitem.html` template.
Now, we only have to render it: first using the `index.html` template - which
adds some more information to our index - then using the
`templates/default.html` template.
~~~~~{.haskell}
renderChain ["index.html", "templates/default.html"] index
~~~~~
Note that the `index.html` in the `renderChain` list is also a template. Now,
you might want to take your time to read the `index.html` template and the other
files in the zip so you understand what is going on here.
## The gist of it
- Hakyll has native support for RSS and Atom feeds.
- The items must contain `$title` and `$description` fields.
- Arrows can be used to copy values in a `Context`.
- You can find blogposts using `getRecursiveContents`.
- The convention is to call them `yyyy-mm-dd-rest-of-title.extension`. This
allows us to sort them easily.
- You can use `createCustomPage` or `createListing` to create custom pages and
simple listings.

View file

@ -1,221 +1,84 @@
---
title: Tags and manipulations
what: enhances our blog with tags and explains context manipulations.
title: Creating feeds
what: adds an rss feed to the simple blog
---
## Context manipulations
## Adding Feeds
Here, have [a zip file]($root/examples/tagblog.zip) for this tutorial.
In this tutorial, we're going to add an RSS feed to the blog we wrote in the
previous part. Here is a [zip file] containing the source.
You probably remember that `Context` objects are 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_.
[zip file]: $root/examples/feedblog.zip
Like rendering actions, _context manipulations_ are also simply
`HakyllAction Context Context` arrows. The `Text.Hakyll.ContextManipulations`
contains some functions to easily construct easy variants.
You will be glad to hear that Hakyll has native support for RSS as well as Atom
feeds[^1]. This simplifies our object a lot.
One of the most general functions is the `renderValue` function. Let's have a
look at it's type.
[^1]: Since Hakyll-2.0
This is the first time that the absolute URL of your site you have to give to
the `hakyll` function actually matters. That's because the specifications of
those feed formats dictate you need the full URL of them.
## Creating a configuration
The first thing to do is creating a configuration for your feed. You could
place this code outside of your main function, as is done in the example.
~~~~~{.haskell}
renderValue :: String
-> String
-> (String -> String)
-> HakyllAction Context Context
myFeedConfiguration = FeedConfiguration
{ feedUrl = "rss.xml"
, feedTitle = "SimpleBlog RSS feed."
, feedDescription = "Simple demo of a feed created with Hakyll."
, feedAuthorName = "Jasper Van der Jeugt"
}
~~~~~
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.
Note that we enter the url of the feed in our configuration. So the function
to render our feed only takes two arguments, the configuration and a list of
items to put in the feed. Let's put the three most recent posts in our feed.
~~~~~{.haskell}
import Data.Char (toUpper)
titleUpper :: HakyllAction Context Context
titleUpper = renderValue "title" "title" $ map toUpper
renderRss myFeedConfiguration (take 3 postPages)
~~~~~
Because the destination `key` is the same as the source `key`, we can also use
the `changeValue` function here.
## But it's not that easy
~~~~~{.haskell}
titleUpper = changeValue "title" $ map toUpper
~~~~~
If you look at our generated RSS feed (build the site), you will see
`$description` tags appearing in our final render. We don't want that! How
did they get there in the first place?
For further reading, refer to the `Text.Hakyll.ContextManipulations`
documentation.
To render feeds, Hakyll expects a number of fields in the renderables you put
in the feed. They are:
## Applying Context Manipulations
Because we're dealing with Arrows again, we can use `>>>` to apply our
manipulations. For example, we could use or title manipulation like this:
~~~~~{.haskell}
renderChain ["templates/default.html"]
(createPage "index.markdown" >>> titleUpper)
~~~~~
## 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 :: HakyllAction Context Context
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`
- `$title`: Title of the item. This is set in our posts, since we use a `title`
metadata field.
- `$url`: Url of the item. This is automatically set by Hakyll, so you shouldn't
worry about it.
- `$description`: A description of our item to appear in the feed reader.
So, we can throw away our `date: ` lines from our posts, and still use `$date`
in our templates.
The latter is obviously the problem: we don't have a description in our posts.
In fact, we would like to copy the `$body` key to the `$description` key, so
people can read the full post in their feed readers.
## Abstracting the post list
## Where arrows come in
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 list = createListingWith url ["templates/postitem.html"]
posts [("title", Left title)]
renderChain ["posts.html", "templates/default.html"] list
~~~~~
Our "render all posts" action can now be written as:
The `Text.Hakyll.ContextManipulations` module contains a number of simple
functions that create Arrows for us. One of these functions is `copyValue`,
which takes a source and a destination key. So, we need to pass our
items through this Arrow first.
~~~~~{.haskell}
renderPostList "posts.html" "All posts" renderablePosts
renderRss myFeedConfiguration $
map (>>> copyValue "body" "description") (take 3 postPages)
~~~~~
## Tag links
And that's that, now our feed gets rendered properly. Exercise for the reader
is to add a Atom feed[^2].
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 = "$root/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 :: HakyllAction Context Context
postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
>>> renderTagLinks tagToUrl
~~~~~
We apply this manipulation when we load the tags.
~~~~~{.haskell}
let renderablePosts =
map ((>>> postManipulation) . createPage) postPaths
~~~~~
So, the `renderTagLinks` function replaces the `$tags` value from
`epic fail, random` to `<a href="$root/tags/epic-fail.html">epic fail</a>, ...`.
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}
type TagMap = Map String [HakyllAction () Context]
readTagMap String [FilePath] -> HakyllAction () TagMap
~~~~~
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.
The first argument given is an "identifier", unique to this tag map. Hakyll
needs this so it can cache the tags.
~~~~~{.haskell}
let tagMap = readTagMap "postTags" postPaths
~~~~~
When we have the `TagMap`, we can need to render a post list for every tag.
There is a function in Hakyll designed more or less for this purpose:
`withTagMap`. This takes a `TagMap` and an action to execute for every tag and
it's associated posts. We pass a small function to it we create ourselves[^1]:
[^1]: Exercise for the reader: why do we use `>>> postManipulation` again here?
~~~~~{.haskell}
let renderListForTag tag posts =
renderPostList (tagToUrl tag)
("Posts tagged " ++ tag)
(map (>>> postManipulation) posts)
withTagMap tagMap renderPostList
~~~~~
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}
renderTagCloud :: (String -> String)
-> Float
-> Float
-> HakyllAction TagMap String
~~~~~
The first 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 a tag
cloud Arrow back. We can add this to our index:
~~~~~{.haskell}
let tagCloud = tagMap >>> renderTagCloud tagToUrl 100 200
index = createListing "index.html"
["templates/postitem.html"]
(take 3 renderablePosts)
[ ("title", Left "Home")
, ("tagcloud", Right tagCloud)
]
renderChain ["index.html", "templates/default.html"] index
~~~~~
[^2]: Hint: look around in the [reference]($root/reference.html).
## The gist of it
- There's some handy, simple functions in `Text.Hakyll.ContextManipulations`.
- Seperate tags by commas and put them in the `$tags` field.
- Use `withTagMap` to render a list for every tag.
- Hakyll can also create tag clouds.
- Hakyll has native support for RSS and Atom feeds.
- The items must contain `$title` and `$description` fields.
- Arrows can be used to copy values in a `Context`.

View file

@ -1,102 +1,221 @@
---
title: Interlude
what: gives some various tips and tricks about Hakyll (quite handy, read this!)
title: Tags and manipulations
what: enhances our blog with tags and explains context manipulations.
---
## Syntax-highlighting
## Context manipulations
Pandoc (which Hakyll uses as a backend) offers powerful syntax highlighting.
To enable this, Pandoc needs to be compiled with highlighting support. If this
is not the case, you can fix this using:
Here, have [a zip file]($root/examples/tagblog.zip) for this tutorial.
~~~~~
[jasper@alice ~]$ cabal install --reinstall -fhighlighting pandoc
~~~~~
You probably remember that `Context` objects are 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_.
## Auto-compilation
Like rendering actions, _context manipulations_ are also simply
`HakyllAction Context Context` arrows. The `Text.Hakyll.ContextManipulations`
contains some functions to easily construct easy variants.
Hakyll features a simple _auto-compilation_ mode. This is invoked by running
~~~~~
[jasper@alice ~]$ ./hakyll preview
Starting hakyll server on port 8000...
~~~~~
Now, Hakyll will recompile your site when you change files, so you can just
refresh in your browser. There is one more thing to note: this will not update
your site automatically when `hakyll.hs` changes. So if you make any changes to
the configuration file, you'll have to compile it again, and then you can enter
`preview` mode again.
## When to rebuild
If you execute a `./hakyll build`, Hakyll will build your site incrementally.
This means it will be very fast, but it will not pick up _all_ changes.
- In case you edited `hakyll.hs`, you first want to compile it again.
- It is generally recommended to do a `./hakyll rebuild` before you deploy your
site.
## Pretty URL's
There is an option in Hakyll to produce pretty URL's, which is disabled by
default because it can be confusing when you're first introduced to Hakyll.
It can be enabled this way:
One of the most general functions is the `renderValue` function. Let's have a
look at it's type.
~~~~~{.haskell}
import Text.Hakyll
import Text.Hakyll.Hakyll
myConfig :: HakyllConfiguration
myConfig = (defaultHakyllConfiguration "http://jaspervdj.be")
{ enableIndexUrl = True
}
main = hakyllWithConfiguration myConfig $ do
-- Further code here
renderValue :: String
-> String
-> (String -> String)
-> HakyllAction Context Context
~~~~~
The effect will be that the internal `toUrl` function will behave differently.
A few examples:
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.
- `about.html` will be rendered to `about/index.html`.
- `posts/2010-02-16-a-post.markdown` will be rendered to
`posts/2010-02-16-a-post/index.html`.
- However, `index.markdown` will still be rendered to `index.html`. Likewise,
`posts/index.html` would be rendered to `posts.index.html`.
As a simple example, let's write a function that puts the `$title` in uppercase.
The benefit of this is simply prettier URL's. That is, if you consider
`example.com/about` prettier than `example.com/about.html`.
~~~~~{.haskell}
import Data.Char (toUpper)
## Default values
titleUpper :: HakyllAction Context Context
titleUpper = renderValue "title" "title" $ map toUpper
~~~~~
At some point, you might want to use a number of global key-value pairs, for
example, `$author`. There are two possible ways to achieve this.
Because the destination `key` is the same as the source `key`, we can also use
the `changeValue` function here.
- There is an option in `HakyllConfiguration` supporting this, called
`additionalContext`. For an example on how to use `HakyllConfiguration`, see
the pretty URL's section above.
~~~~~{.haskell}
titleUpper = changeValue "title" $ map toUpper
~~~~~
- Another option is to use a `defaults.markdown` file, simply containing some
metadata, and then `combine` this file with other pages. The advantage is
that autocompilation mode will pick up changes in this file[^1].
For further reading, refer to the `Text.Hakyll.ContextManipulations`
documentation.
[^1]: Original idea by zenzike.
## Applying Context Manipulations
## Markup in templates
Because we're dealing with Arrows again, we can use `>>>` to apply our
manipulations. For example, we could use or title manipulation like this:
Most of the examples in these tutorials use HTML for templates. However, since
Hakyll 2.2, it is possible use other markup languages in your templates. Simply
use an appropriate extension, and Hakyll will pick it up. For example, you could
write your `templates/post.markdown` template as:
~~~~~{.haskell}
renderChain ["templates/default.html"]
(createPage "index.markdown" >>> titleUpper)
~~~~~
# $title
## Rendering dates
_On $date_
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`.
$body
~~~~~{.haskell}
postManipulation :: HakyllAction Context Context
postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
~~~~~
__Warning__: you shouldn't use markdown for your "root" template, as these
templates will never insert things like the doctype for you -- so you always
need at least one top-level HTML template.
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 list = createListingWith url ["templates/postitem.html"]
posts [("title", Left title)]
renderChain ["posts.html", "templates/default.html"] list
~~~~~
Our "render all posts" action can now be written as:
~~~~~{.haskell}
renderPostList "posts.html" "All posts" renderablePosts
~~~~~
## 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 = "$root/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 :: HakyllAction Context Context
postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
>>> renderTagLinks tagToUrl
~~~~~
We apply this manipulation when we load the tags.
~~~~~{.haskell}
let renderablePosts =
map ((>>> postManipulation) . createPage) postPaths
~~~~~
So, the `renderTagLinks` function replaces the `$tags` value from
`epic fail, random` to `<a href="$root/tags/epic-fail.html">epic fail</a>, ...`.
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}
type TagMap = Map String [HakyllAction () Context]
readTagMap String [FilePath] -> HakyllAction () TagMap
~~~~~
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.
The first argument given is an "identifier", unique to this tag map. Hakyll
needs this so it can cache the tags.
~~~~~{.haskell}
let tagMap = readTagMap "postTags" postPaths
~~~~~
When we have the `TagMap`, we can need to render a post list for every tag.
There is a function in Hakyll designed more or less for this purpose:
`withTagMap`. This takes a `TagMap` and an action to execute for every tag and
it's associated posts. We pass a small function to it we create ourselves[^1]:
[^1]: Exercise for the reader: why do we use `>>> postManipulation` again here?
~~~~~{.haskell}
let renderListForTag tag posts =
renderPostList (tagToUrl tag)
("Posts tagged " ++ tag)
(map (>>> postManipulation) posts)
withTagMap tagMap renderPostList
~~~~~
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}
renderTagCloud :: (String -> String)
-> Float
-> Float
-> HakyllAction TagMap String
~~~~~
The first 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 a tag
cloud Arrow back. We can add this to our index:
~~~~~{.haskell}
let tagCloud = tagMap >>> renderTagCloud tagToUrl 100 200
index = createListing "index.html"
["templates/postitem.html"]
(take 3 renderablePosts)
[ ("title", Left "Home")
, ("tagcloud", Right tagCloud)
]
renderChain ["index.html", "templates/default.html"] index
~~~~~
## The gist of it
- There's some handy, simple functions in `Text.Hakyll.ContextManipulations`.
- Seperate tags by commas and put them in the `$tags` field.
- Use `withTagMap` to render a list for every tag.
- Hakyll can also create tag clouds.

View file

@ -1,104 +1,85 @@
---
title: CategoryBlog
what: explains how to use categories instead of tags
title: Interlude
what: gives some various tips and tricks about Hakyll (quite handy, read this!)
---
## Categories
## Syntax-highlighting
Most people familiar with "tags" will also know the concept "categories".
Pandoc (which Hakyll uses as a backend) offers powerful syntax highlighting.
To enable this, Pandoc needs to be compiled with highlighting support. If this
is not the case, you can fix this using:
![Tags illustration]($root/images/tutorial8-tags.png)
In fact, tags are harder to implement because they have to be represented as a
many-to-many relation, and categories are a simple 1-to-many relation.
![Tags illustration]($root/images/tutorial8-categories.png)
This is also the reason you can "simulate" categories using tags. In this
tutorial we will adapt the blog to use categories instead of tags. Here is
[a zip file]($root/examples/categoryblog.zip) containing the files used in this
tutorial.
## About category support
Categories are simpler, but they are usually used in custom ways. That's why
Hakyll provides less "standard" functions to deal with them. But this gives us
another chance to learn some of the things we can do with Hakyll.
## Reading Categories
Tags are located in the `tags` metadata field. Since one post can only belong
in one category, a different approach was chosen here. The category of a post
is determined by the subfolder it is in. Here you see the directory layout for
our posts using categories:
posts
|-- coding
| |-- 2009-11-05-a-first-post.markdown
| |-- 2009-11-28-a-third-post.markdown
| `-- 2009-12-04-this-blog-aint-dead.markdown
`-- random
|-- 2009-11-10-another-post.markdown
`-- 2009-12-23-almost-christmas.markdown
Because we find all our posts in different subdirectories, sorting them is a
little harder: we still want them sorted by date, so it boils down to sorting
them by "base name". I hope it does not surprise you Hakyll provides a function
for that:
~~~~~{.haskell}
postPaths <- liftM (reverse . sortByBaseName)
(getRecursiveContents "posts")
~~~~~
[jasper@alice ~]$ cabal install --reinstall -fhighlighting pandoc
~~~~~
We reverse them again, because we want the most recent posts first. Now, we can
use the `readCategoryMap` function instead of `readTagMap`, which has the same
signature, but assigns categories based on the folders the posts are in.
## Auto-compilation
~~~~~{.haskell}
categoryMap <- readCategoryMap "categoryMap" renderablePosts
Hakyll features a simple _auto-compilation_ mode. This is invoked by running
~~~~~
[jasper@alice ~]$ ./hakyll preview
Starting hakyll server on port 8000...
~~~~~
The rest of the `hakyll.hs` is very similar to the one in the previous
tutorial, except we want to render a category list instead of a tag cloud.
Now, Hakyll will recompile your site when you change files, so you can just
refresh in your browser. There is one more thing to note: this will not update
your site automatically when `hakyll.hs` changes. So if you make any changes to
the configuration file, you'll have to compile it again, and then you can enter
`preview` mode again.
## Rendering a category list
## When to rebuild
Because rendering a category list is quite easy, and it would be hard to
write a "general" function for this, hakyll does not provide such a function --
but it is not hard to write. First, we write an auxiliary function that produces
a list item for one category:
If you execute a `./hakyll build`, Hakyll will build your site incrementally.
This means it will be very fast, but it will not pick up _all_ changes.
- In case you edited `hakyll.hs`, you first want to compile it again.
- It is generally recommended to do a `./hakyll rebuild` before you deploy your
site.
## Pretty URL's
There is an option in Hakyll to produce pretty URL's, which is disabled by
default because it can be confusing when you're first introduced to Hakyll.
It can be enabled this way:
~~~~~{.haskell}
categoryListItem category posts =
"<li>" ++ link category (categoryToUrl category)
++ " - " ++ show (length posts) ++ " items.</li>"
import Text.Hakyll
import Text.Hakyll.Hakyll
myConfig :: HakyllConfiguration
myConfig = (defaultHakyllConfiguration "http://jaspervdj.be")
{ enableIndexUrl = True
}
main = hakyllWithConfiguration myConfig $ do
-- Further code here
~~~~~
This is nothing more that some basic string concatenation to create a `li` HTML
element. The function that applies this on every element in the `TagMap` is more
interesting:
The effect will be that the internal `toUrl` function will behave differently.
A few examples:
~~~~~{.haskell}
categoryList :: HakyllAction TagMap String
categoryList = arr $ uncurry categoryListItem <=< toList
~~~~~
- `about.html` will be rendered to `about/index.html`.
- `posts/2010-02-16-a-post.markdown` will be rendered to
`posts/2010-02-16-a-post/index.html`.
- However, `index.markdown` will still be rendered to `index.html`. Likewise,
`posts/index.html` would be rendered to `posts.index.html`.
This function might seem a little harder to understand if you are not familiar
with the `<=<` operator -- but it's just right-to-left monad composition in the
list monad. `uncurry categoryListItem <=< toList` is a pure function we want to
execute on the `TagMap`. But this is not possible in Hakyll[^1]. We need to make
an arrow of this function. The `arr` function solves this problem easily.
The benefit of this is simply prettier URL's. That is, if you consider
`example.com/about` prettier than `example.com/about.html`.
[^1]: This is a feature, not a bug. It helps dependency handling.
## Default values
We then add this to our index page, and we are done. Feel free to hack around
with the source code. If you still have questions, feel free to ask them at the
[google discussion group](http://groups.google.com/group/hakyll).
At some point, you might want to use a number of global key-value pairs, for
example, `$author`. There are two possible ways to achieve this.
## The gist of it
- There is an option in `HakyllConfiguration` supporting this, called
`additionalContext`. For an example on how to use `HakyllConfiguration`, see
the pretty URL's section above.
- Hakyll supports categories as well as tags.
- Tags are actually a generalization of categories.
- Use `readCategoryMap` to read categories.
- You need to write some custom functions to render category lists etc.
- Another option is to use a `defaults.markdown` file, simply containing some
metadata, and then `combine` this file with other pages. The advantage is
that autocompilation mode will pick up changes in this file[^1].
[^1]: Original idea by zenzike.

View file

@ -0,0 +1,104 @@
---
title: CategoryBlog
what: explains how to use categories instead of tags
---
## Categories
Most people familiar with "tags" will also know the concept "categories".
![Tags illustration]($root/images/tutorial8-tags.png)
In fact, tags are harder to implement because they have to be represented as a
many-to-many relation, and categories are a simple 1-to-many relation.
![Tags illustration]($root/images/tutorial8-categories.png)
This is also the reason you can "simulate" categories using tags. In this
tutorial we will adapt the blog to use categories instead of tags. Here is
[a zip file]($root/examples/categoryblog.zip) containing the files used in this
tutorial.
## About category support
Categories are simpler, but they are usually used in custom ways. That's why
Hakyll provides less "standard" functions to deal with them. But this gives us
another chance to learn some of the things we can do with Hakyll.
## Reading Categories
Tags are located in the `tags` metadata field. Since one post can only belong
in one category, a different approach was chosen here. The category of a post
is determined by the subfolder it is in. Here you see the directory layout for
our posts using categories:
posts
|-- coding
| |-- 2009-11-05-a-first-post.markdown
| |-- 2009-11-28-a-third-post.markdown
| `-- 2009-12-04-this-blog-aint-dead.markdown
`-- random
|-- 2009-11-10-another-post.markdown
`-- 2009-12-23-almost-christmas.markdown
Because we find all our posts in different subdirectories, sorting them is a
little harder: we still want them sorted by date, so it boils down to sorting
them by "base name". I hope it does not surprise you Hakyll provides a function
for that:
~~~~~{.haskell}
postPaths <- liftM (reverse . sortByBaseName)
(getRecursiveContents "posts")
~~~~~
We reverse them again, because we want the most recent posts first. Now, we can
use the `readCategoryMap` function instead of `readTagMap`, which has the same
signature, but assigns categories based on the folders the posts are in.
~~~~~{.haskell}
categoryMap <- readCategoryMap "categoryMap" renderablePosts
~~~~~
The rest of the `hakyll.hs` is very similar to the one in the previous
tutorial, except we want to render a category list instead of a tag cloud.
## Rendering a category list
Because rendering a category list is quite easy, and it would be hard to
write a "general" function for this, hakyll does not provide such a function --
but it is not hard to write. First, we write an auxiliary function that produces
a list item for one category:
~~~~~{.haskell}
categoryListItem category posts =
"<li>" ++ link category (categoryToUrl category)
++ " - " ++ show (length posts) ++ " items.</li>"
~~~~~
This is nothing more that some basic string concatenation to create a `li` HTML
element. The function that applies this on every element in the `TagMap` is more
interesting:
~~~~~{.haskell}
categoryList :: HakyllAction TagMap String
categoryList = arr $ uncurry categoryListItem <=< toList
~~~~~
This function might seem a little harder to understand if you are not familiar
with the `<=<` operator -- but it's just right-to-left monad composition in the
list monad. `uncurry categoryListItem <=< toList` is a pure function we want to
execute on the `TagMap`. But this is not possible in Hakyll[^1]. We need to make
an arrow of this function. The `arr` function solves this problem easily.
[^1]: This is a feature, not a bug. It helps dependency handling.
We then add this to our index page, and we are done. Feel free to hack around
with the source code. If you still have questions, feel free to ask them at the
[google discussion group](http://groups.google.com/group/hakyll).
## The gist of it
- Hakyll supports categories as well as tags.
- Tags are actually a generalization of categories.
- Use `readCategoryMap` to read categories.
- You need to write some custom functions to render category lists etc.