summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJasper Van der Jeugt <jaspervdj@gmail.com>2010-08-06 13:28:23 +0200
committerJasper Van der Jeugt <jaspervdj@gmail.com>2010-08-06 13:28:23 +0200
commitd3b87ba2590480a05d8b13770889ac1f14a2b573 (patch)
treef6b4097f460e99a775befdf53cc3b7b27d9b389a
parentd0a9d010629566f39b012848a1b45fdf93f6bc33 (diff)
downloadhakyll-d3b87ba2590480a05d8b13770889ac1f14a2b573.tar.gz
Add tutorial about templates
-rw-r--r--examples/hakyll/tutorials/part03.markdown2
-rw-r--r--examples/hakyll/tutorials/part04.markdown183
-rw-r--r--examples/hakyll/tutorials/part05.markdown177
-rw-r--r--examples/hakyll/tutorials/part06.markdown239
-rw-r--r--examples/hakyll/tutorials/part07.markdown247
-rw-r--r--examples/hakyll/tutorials/part08.markdown135
-rw-r--r--examples/hakyll/tutorials/part09.markdown104
7 files changed, 565 insertions, 522 deletions
diff --git a/examples/hakyll/tutorials/part03.markdown b/examples/hakyll/tutorials/part03.markdown
index 3b04f9a..0ba633e 100644
--- a/examples/hakyll/tutorials/part03.markdown
+++ b/examples/hakyll/tutorials/part03.markdown
@@ -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
diff --git a/examples/hakyll/tutorials/part04.markdown b/examples/hakyll/tutorials/part04.markdown
index f21819f..22fc846 100644
--- a/examples/hakyll/tutorials/part04.markdown
+++ b/examples/hakyll/tutorials/part04.markdown
@@ -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.
+Simple templates are simply HTML files, with `$identifiers`. An example:
-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
+~~~~~{.html}
+<html>
+ <head>
+ <title>$title</title>
+ </head>
+ <body>
+ $body
+ </body>
+</html>
~~~~~
-[^1]: Since Hakyll-1.3 onwards.
+## Markup in templates
-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:
+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:
-~~~~~{.html}
-<li>
- <a href="$$root/$url">$title</a>
- - <em>$date</em> - by <em>$author</em>
-</li>
-~~~~~
+ # $title
-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:
+ _On $date_
-~~~~~{.haskell}
-let index = createListing "index.html"
- ["templates/postitem.html"]
- (take 3 postPages)
- [("title", Left "Home")]
-~~~~~
+ $body
-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.
+__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.
-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.
+## Hamlet templates
-~~~~~{.haskell}
-renderChain ["index.html", "templates/default.html"] index
-~~~~~
+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:
-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.
+ !!!
+ %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$
-## The gist of it
+Hakyll will recognise hamlet templates automatically by the `.hamlet` extension.
-- 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.
+[hamlet]: http://docs.yesodweb.com/hamlet/
diff --git a/examples/hakyll/tutorials/part05.markdown b/examples/hakyll/tutorials/part05.markdown
index d64dc55..f21819f 100644
--- a/examples/hakyll/tutorials/part05.markdown
+++ b/examples/hakyll/tutorials/part05.markdown
@@ -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
+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 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.
+~~~~~{.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:
-## Creating a configuration
+~~~~~{.haskell}
+postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
+~~~~~
-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.
+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}
-myFeedConfiguration = FeedConfiguration
- { feedUrl = "rss.xml"
- , feedTitle = "SimpleBlog RSS feed."
- , feedDescription = "Simple demo of a feed created with Hakyll."
- , feedAuthorName = "Jasper Van der Jeugt"
- }
+let postPages = map createPage postPaths
~~~~~
-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.
+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}
-renderRss myFeedConfiguration (take 3 postPages)
+mapM_ (renderChain [ "templates/post.html"
+ , "templates/default.html"
+ ]) postPages
~~~~~
-## But it's not that easy
+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.
-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?
+## Creating listings.
-To render feeds, Hakyll expects a number of fields in the renderables you put
-in the feed. They are:
+`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`.
-- `$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.
+~~~~~{.haskell}
+createCustomPage :: FilePath
+ -> [(String, Either String (HakyllAction () String)]
+ -> HakyllAction () Context
+~~~~~
-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.
+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:
-## Where arrows come in
+- `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.
-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.
+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}
-renderRss myFeedConfiguration $
- map (>>> copyValue "body" "description") (take 3 postPages)
+createListing :: FilePath
+ -> [FilePath]
+ -> [HakyllAction () Context]
+ -> [(String, Either String (HakyllAction () String)]
+ -> HakyllAction () Context
~~~~~
-And that's that, now our feed gets rendered properly. Exercise for the reader
-is to add a Atom feed[^2].
+[^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
+~~~~~
-[^2]: Hint: look around in the [reference]($root/reference.html).
+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.
diff --git a/examples/hakyll/tutorials/part06.markdown b/examples/hakyll/tutorials/part06.markdown
index d017a1e..d64dc55 100644
--- a/examples/hakyll/tutorials/part06.markdown
+++ b/examples/hakyll/tutorials/part06.markdown
@@ -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
-~~~~~{.haskell}
-renderValue :: String
- -> String
- -> (String -> String)
- -> HakyllAction Context Context
-~~~~~
+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.
-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.
+## Creating a configuration
-As a simple example, let's write a function that puts the `$title` in uppercase.
+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}
-import Data.Char (toUpper)
-
-titleUpper :: HakyllAction Context Context
-titleUpper = renderValue "title" "title" $ map toUpper
+myFeedConfiguration = FeedConfiguration
+ { feedUrl = "rss.xml"
+ , feedTitle = "SimpleBlog RSS feed."
+ , feedDescription = "Simple demo of a feed created with Hakyll."
+ , feedAuthorName = "Jasper Van der Jeugt"
+ }
~~~~~
-Because the destination `key` is the same as the source `key`, we can also use
-the `changeValue` function here.
+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}
-titleUpper = changeValue "title" $ map toUpper
+renderRss myFeedConfiguration (take 3 postPages)
~~~~~
-For further reading, refer to the `Text.Hakyll.ContextManipulations`
-documentation.
-
-## Applying Context Manipulations
+## But it's not that easy
-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)
-~~~~~
+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?
-## Rendering dates
+To render feeds, Hakyll expects a number of fields in the renderables you put
+in the feed. They are:
-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.
-
-## 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.
+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.
-~~~~~{.haskell}
-let tagMap = readTagMap "postTags" postPaths
-~~~~~
+## Where arrows come in
-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?
+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}
-let renderListForTag tag posts =
- renderPostList (tagToUrl tag)
- ("Posts tagged " ++ tag)
- (map (>>> postManipulation) posts)
-withTagMap tagMap renderPostList
+renderRss myFeedConfiguration $
+ map (>>> copyValue "body" "description") (take 3 postPages)
~~~~~
-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.
+And that's that, now our feed gets rendered properly. Exercise for the reader
+is to add a Atom feed[^2].
-~~~~~{.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`.
diff --git a/examples/hakyll/tutorials/part07.markdown b/examples/hakyll/tutorials/part07.markdown
index 87c999a..d017a1e 100644
--- a/examples/hakyll/tutorials/part07.markdown
+++ b/examples/hakyll/tutorials/part07.markdown
@@ -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.
+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_.
+
+Like rendering actions, _context manipulations_ are also simply
+`HakyllAction Context Context` arrows. The `Text.Hakyll.ContextManipulations`
+contains some functions to easily construct easy variants.
+
+One of the most general functions is the `renderValue` function. Let's have a
+look at it's type.
+
+~~~~~{.haskell}
+renderValue :: String
+ -> String
+ -> (String -> String)
+ -> HakyllAction Context Context
+~~~~~
+
+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 :: HakyllAction Context Context
+titleUpper = renderValue "title" "title" $ map toUpper
~~~~~
-[jasper@alice ~]$ cabal install --reinstall -fhighlighting pandoc
+
+Because the destination `key` is the same as the source `key`, we can also use
+the `changeValue` function here.
+
+~~~~~{.haskell}
+titleUpper = changeValue "title" $ map toUpper
~~~~~
-## Auto-compilation
+For further reading, refer to the `Text.Hakyll.ContextManipulations`
+documentation.
-Hakyll features a simple _auto-compilation_ mode. This is invoked by running
+## 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)
~~~~~
-[jasper@alice ~]$ ./hakyll preview
-Starting hakyll server on port 8000...
+
+## 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"
~~~~~
-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.
+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
+~~~~~
-## When to rebuild
+Our "render all posts" action can now be written as:
-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.
+~~~~~{.haskell}
+renderPostList "posts.html" "All posts" renderablePosts
+~~~~~
-- 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.
+## Tag links
-## Pretty URL's
+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:
-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.
+~~~~~{.haskell}
+tagToUrl tag = "$root/tags/" ++ removeSpaces tag ++ ".html"
+~~~~~
-It can be enabled this way:
+`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}
-import Text.Hakyll
-import Text.Hakyll.Hakyll
+postManipulation :: HakyllAction Context Context
+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
+ >>> renderTagLinks tagToUrl
+~~~~~
-myConfig :: HakyllConfiguration
-myConfig = (defaultHakyllConfiguration "http://jaspervdj.be")
- { enableIndexUrl = True
- }
+We apply this manipulation when we load the tags.
-main = hakyllWithConfiguration myConfig $ do
- -- Further code here
+~~~~~{.haskell}
+let renderablePosts =
+ map ((>>> postManipulation) . createPage) postPaths
~~~~~
-The effect will be that the internal `toUrl` function will behave differently.
-A few examples:
+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.
-- `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`.
+## The Tag Map
-The benefit of this is simply prettier URL's. That is, if you consider
-`example.com/about` prettier than `example.com/about.html`.
+Hakyll provides a function called `readTagMap`. Let's inspect it's type.
-## Default values
+~~~~~{.haskell}
+type TagMap = Map String [HakyllAction () Context]
+readTagMap String [FilePath] -> HakyllAction () TagMap
+~~~~~
-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.
+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.
-- 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}
+let tagMap = readTagMap "postTags" postPaths
+~~~~~
-- 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].
+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]: Original idea by zenzike.
+[^1]: Exercise for the reader: why do we use `>>> postManipulation` again here?
-## Markup in templates
+~~~~~{.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.
-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:
+## A Tag Cloud
- # $title
+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.
- _On $date_
+~~~~~{.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
+~~~~~
- $body
+## The gist of it
-__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.
+- 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.
diff --git a/examples/hakyll/tutorials/part08.markdown b/examples/hakyll/tutorials/part08.markdown
index 4cc1d43..bfb986b 100644
--- a/examples/hakyll/tutorials/part08.markdown
+++ b/examples/hakyll/tutorials/part08.markdown
@@ -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.
+~~~~~
+[jasper@alice ~]$ cabal install --reinstall -fhighlighting pandoc
+~~~~~
-![Tags illustration]($root/images/tutorial8-categories.png)
+## Auto-compilation
-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.
+Hakyll features a simple _auto-compilation_ mode. This is invoked by running
-## About category support
+~~~~~
+[jasper@alice ~]$ ./hakyll preview
+Starting hakyll server on port 8000...
+~~~~~
-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.
+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.
-## Reading Categories
+## When to rebuild
-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:
+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.
- 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
+- 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.
-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:
+## Pretty URL's
-~~~~~{.haskell}
-postPaths <- liftM (reverse . sortByBaseName)
- (getRecursiveContents "posts")
-~~~~~
+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.
-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.
+It can be enabled this way:
~~~~~{.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.
+import Text.Hakyll
+import Text.Hakyll.Hakyll
-## Rendering a category list
+myConfig :: HakyllConfiguration
+myConfig = (defaultHakyllConfiguration "http://jaspervdj.be")
+ { enableIndexUrl = True
+ }
-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>"
+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`.
+
+The benefit of this is simply prettier URL's. That is, if you consider
+`example.com/about` prettier than `example.com/about.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.
+## Default values
-[^1]: This is a feature, not a bug. It helps dependency handling.
+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.
-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).
+- 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.
-## The gist of it
+- 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].
-- 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.
+[^1]: Original idea by zenzike.
diff --git a/examples/hakyll/tutorials/part09.markdown b/examples/hakyll/tutorials/part09.markdown
new file mode 100644
index 0000000..4cc1d43
--- /dev/null
+++ b/examples/hakyll/tutorials/part09.markdown
@@ -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.