summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJasper Van der Jeugt <jaspervdj@gmail.com>2010-01-19 17:17:48 +0100
committerJasper Van der Jeugt <jaspervdj@gmail.com>2010-01-19 17:17:48 +0100
commit0ba0eac8d96c11d13163c7ec184392513de7ca77 (patch)
treeca806e7fc8d8f049e251a4962136ce99fa2a004c
parentdb0eef84705ebc25b3886cc94125dfebb84a45a0 (diff)
downloadhakyll-0ba0eac8d96c11d13163c7ec184392513de7ca77.tar.gz
Wrote another tutorial, and updated some tutorials.
-rw-r--r--examples/hakyll/tutorial1.markdown14
-rw-r--r--examples/hakyll/tutorial2.markdown221
-rw-r--r--examples/hakyll/tutorial3.markdown222
-rw-r--r--examples/hakyll/tutorial4.markdown267
-rw-r--r--examples/hakyll/tutorial5.markdown211
-rw-r--r--examples/morepages/about.markdown27
-rw-r--r--examples/morepages/css/default.css27
-rw-r--r--examples/morepages/footer.markdown16
-rw-r--r--examples/morepages/hakyll.hs13
-rw-r--r--examples/morepages/index.markdown24
-rw-r--r--examples/morepages/products.markdown23
-rw-r--r--examples/morepages/templates/default.html22
12 files changed, 699 insertions, 388 deletions
diff --git a/examples/hakyll/tutorial1.markdown b/examples/hakyll/tutorial1.markdown
index fafaea7..f3c99aa 100644
--- a/examples/hakyll/tutorial1.markdown
+++ b/examples/hakyll/tutorial1.markdown
@@ -66,12 +66,12 @@ metadata. The metadata is placed in the file header and surrouded by `---`
lines. Each line should contain a `key: value` pair. Let's have a look at the
`about.markdown` page.
-> ---
-> title: About
-> ---
-> Nullam imperdiet sodales orci vitae molestie.
-> Nunc quam orci, pharetra a rhoncus vitae,
-> eleifend id felis. Suspendisse potenti...
+ ---
+ title: About
+ ---
+ Nullam imperdiet sodales orci vitae molestie.
+ Nunc quam orci, pharetra a rhoncus vitae,
+ eleifend id felis. Suspendisse potenti...
This contains one `key: value` pair, namely `title: About`. The rest of the
file is treated as markdown by pandoc. If you want to know more about
@@ -209,4 +209,4 @@ questions, feel free to ask them on the
[google discussion group](http://groups.google.com/group/hakyll).
If you feel comfortable with the basics, here is a next tutorial:
-[building a simple blog](tutorial2.html).
+[more about writing pages](tutorial2.html).
diff --git a/examples/hakyll/tutorial2.markdown b/examples/hakyll/tutorial2.markdown
index 0fa4002..d4c3d2f 100644
--- a/examples/hakyll/tutorial2.markdown
+++ b/examples/hakyll/tutorial2.markdown
@@ -1,162 +1,159 @@
---
title: Tutorial (Part II)
-what: creates a simple blog
+what: elaborates a little on writing pages and templates
---
-## Creating a simple blog with Hakyll
+## The structure of a Page
-After we created a simple brochure site, we're going to try something more
-advanced: we are going to create a simple blog system.
+The most important thing to realize is that a `Page` is just a key-value
+mapping. Another example:
-A [zip file containing the source](examples/simpleblog.zip) for this
-tutorial is also available.
+ ---
+ title: About
+ author: Mia Wallace
+ ---
+ Hello there! This is
+ a simple about page.
-Blogs, as you probably know, are composed of posts. In Hakyll, we're going
-to use simple pages for posts. All posts are located in the `posts`
-directory. But we're not going to use the `directory` command here - you will
-see why later. First, some trivial things like css.
+This will produce the following mapping:
-~~~~~{.haskell}
-main = hakyll $ do
- directory css "css"
-~~~~~
+- `title`: About
+- `author`: Mia Wallace
+- `body`: Hello there! This is a simple about page.
-## Finding the posts, and a bit about renderables
+`body` is the traditional name for the main body part of a page. If the page has
+a `.markdown` extension for example, this would also be rendered by pandoc. But
+pages are more flexible. The following is also a valid page:
-`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
-provide us with all the blog posts. The blog posts have a
-`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
-can sort them easily, you could of course name them whatever you want. They
-contain some metadata, too:
+ Hello there! This is
+ a simple about page.
-> 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...
+This will produce one key-value pair:
-Now, we find the posts and sort them reversed:
+- `body`: Hello there! This is a simple about page.
-~~~~~{.haskell}
--- Find all post paths.
-postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
-~~~~~
+But the `Page` parser can do more than this. You can add extra sections, apart
+from the body, and even leave out the body.
+
+ ---
+ author: Vincent Vega
+
+ --- prelude
+ A small introduction goes here. I can write *markdown* here, by the way. Well,
+ assuming this page has a `.markdown` extension.
+
+ --- main
+ I can write some more things here.
+
+This will produce the following:
+
+- `author`: Vincent Vega
+- `prelude`: A small introduction goes here. I can write *markdown* here, by the
+ way. Well, assuming this page has a `.markdown` extension.
+- `main`: I can write some more things here.
+
+The example from this tutorial (we will see later) uses this to build a
+three-column system for the website, separating content from layout.
+
+## Combining pages
-Our `postPaths` value is now of the type `[FilePath]`. `FilePath` is no
-instance of `Renderable`, but `PagePath` is:
+Now you know that `Page`s, and `Renderable`s in general, are basically nothing
+more than key-values mappings, it is time to abuse this fact. There is another
+`Renderable` type we haven't talked about before: a `CombinedRenderable`.
+
+The type signature of the `combine` function does a pretty good job at
+explaining it:
~~~~~{.haskell}
-let renderablePosts = map createPagePath postPaths
+combine :: (Renderable a, Renderable b)
+ => a -> b -> CombinedRenderable a b
~~~~~
-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:
+This means we can take two `Renderable` values and combine them. This is
+basically a `Map.union`: The result will contain all keys from `a`, and all
+keys from `b`. If a key is present in both `Renderable`s, the value from `a`
+will be chosen. This is, for example, always the case with an `url` (since
+all `Renderable` types always have an url).
+
+Combining two `Renderable`s, but setting a different `url` is quite common, so
+there is another function that helps us here:
~~~~~{.haskell}
-mapM_ (renderChain [ "templates/post.html"
- , "templates/default.html"
- ]) renderablePosts
+combineWithURL :: (Renderable a, Renderable b)
+ => FilePath -> a -> b -> CombinedRenderable a b
~~~~~
-Remember that the `renderChain` works by rendering the datatype using the first
-template, creating a new page with the render result in the `body` field, and so
-on until it has been rendered with all templates.
+## The example
-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.
+Now that we have the tools, we'll get on to the example. This time, we'll
+be making a more advanced brochure site. Here
+[is a zip file](examples/morepages.zip) containing the source code for the
+tutorial.
-## Custom Pages
+Every page consists of three sections, originally named `section1`, `section2`
+and `section3`. So our pages look more or less like this:
-Currently, there are 3 renderable datatypes in Hakyll:
+ ---
+ title: About
-- `Page`: The result of any rendering action. It is generally not recommended
- to use pages a lot, because they cannot check dependencies (and therefore,
- you would always regenerate your entire site if you use pages the wrong way).
-- `PagePath`: Basically just a `FilePath` in a box. Internally, this will use
- a `Page` for rendering, but `PagePath` provides better dependency checking
- and works on a higher level.
-- `CustomPage`: Basically the name says it - the preferred way of creating
- custom pages in Hakyll.
+ --- section1
+ ## Mattis
+ Nullam imperdiet sodales orci vitae molestie. Nunc...
-We will use a `CustomPage` here. Basically, all `Renderable` datatypes are in
-the end just `key: value` mappings. A CustomPage is created using the
-`createCustomPage` function, which has the following type signature:
-
-~~~~~{.haskell}
-createCustomPage :: FilePath
- -> [FilePath]
- -> [(String, Either String (IO String)]
-~~~~~
+ --- section2
+ ## Orci
+ Vivamus eget mauris sit amet nulla laoreet lobortis. Nulla in...
-The first argument is the `url` of the page to generate. For our index page,
-this will be, `index.html`. The second argument is _a list of dependencies_.
-Basically, you should here give a list of files on which your custom page
-depends.
+ --- section3
+ ## Augue
+ In urna ante, pulvinar et imperdiet nec, fermentum ac...
-The last argument is obviously our `key: value` mapping. But why the `Either`?
-This, once again, is about dependency handling. The idea is that you can choose
-which type to use for the value:
+The cool thing is we do not have to specify how these will be layed out. In our
+template, we decide to use a simple three column system:
-- `String`: Simply a `String`.
-- `IO String`: Here, you can give an arbitrary `IO` action that will result
- in a String. However - this action _will not be executed_ when the file
- in `_site` is up-to-date.
+~~~~~{.html}
+<div class="column"> $section1 </div>
+<div class="column"> $section2 </div>
+<div class="column"> $section3 </div>
+~~~~~
-First, let us define this `IO String` for our index page. We want to render
-every post using a simple template:
+The columns are then floated using css. So far so good, but what if we wanted
+an additional text block on every page? An easy solution would be to add this
+to the template, but then our layout-content separation idea will be broken
+again. So we simply add to the template:
~~~~~{.html}
-<li>
- <a href="$$root/$url">$title</a>
- - <em>$date</em> - by <em>$author</em>
-</li>
+<div class="footer"> $footer </div>
~~~~~
-When every post is rendered with this template, we then want to concatenate the
-result. Since rendering and concatenating is pretty common, Hakyll provides us
-with a high-level function to do this.
+And now we will use `combine` to put the footer on every page. We write a small
+auxiliary function that combines a given `Renderable` with the footer:
~~~~~{.haskell}
-let recentPosts = renderAndConcat "templates/postitem.html"
- (take 3 renderablePosts)
+withFooter a = a `combine` createPagePath "footer.markdown"
~~~~~
-Now, creating our custom page is fairly straight-forward:
+Now, were we previously wrote:
~~~~~{.haskell}
-createCustomPage "index.html"
- ("templates/postitem.html" : take 3 postPaths)
- [ ("title", Left "All posts")
- , ("posts", Right recentPosts)
- ]
+render "about.markdown"
+where render = renderChain ["templates/default.html"]
+ . createPagePath
~~~~~
-You can see our three arguments here. We're rendering `index.html`, then we tell
-Hakyll on what files it depends - here the `templates/postitem.html` template
-and the latest 3 posts. Finally, we give a `title` value to substitute in the
-template, and the result of our concatenation. Of course, we also need to render
-this custom page:
+We simply have to add our footer:
~~~~~{.haskell}
-renderChain ["index.html", "templates/default.html"] $
- createCustomPage "index.html"
- ("templates/postitem.html" : take 3 postPaths)
- [ ("title", Left "All posts")
- , ("posts", Right recentPosts)
- ]
+render "about.markdown"
+where render = renderChain ["templates/default.html"]
+ . withFooter
+ . createPagePath
~~~~~
-Note that the `index.html` in the `renderChain` list is also a template.
+And now every page will include the footer.
-## That's that
+## That's all folks
-If you have any more questions, feel free to ask them on the
+I hope this tutorial was clear enough to teach you the concepts of pages and
+combining renderables. As always, questions and feedback are welcome at the
[google discussion group](http://groups.google.com/group/hakyll).
-
-There is a [next tutorial](tutorial3.html), explaining how to add an RSS feed
-to our sample blog.
diff --git a/examples/hakyll/tutorial3.markdown b/examples/hakyll/tutorial3.markdown
index e241d63..80f90e8 100644
--- a/examples/hakyll/tutorial3.markdown
+++ b/examples/hakyll/tutorial3.markdown
@@ -1,124 +1,162 @@
---
title: Tutorial (Part III)
-what: adds an RSS feed to the blog from the previous tutorial
+what: creates a simple blog
---
-## Adding RSS to our simple blog
-
-In this tutorial, we're going to add an RSS feed to the blog we wrote in
-[the previous tutorial](tutorial2.html). Here is a
-[zip file containing the source](examples/rssblog.zip).
-
-An RSS feed looks like this:
-
-~~~~~{.xml}
-<?xml version="1.0" ?>
-<rss version="2.0">
- <channel>
- <title>The SimpleBlog</title>
- <link>http://example.com/</link>
- <description>Simple blog in hakyll</description>
- <item>
- <title>Title goes here</title>
- <link>http://example.com/post.html</link>
- <description>
- A description is optional.
- </description>
- </item>
- </channel>
-</rss>
-~~~~~
+## Creating a simple blog with Hakyll
-Note that, obviously, there can be more than one item. We're going to use a
-template to render this. This is where `templates/rss.xml` comes in:
-
-~~~~~{.xml}
-<?xml version="1.0" ?>
-<rss version="2.0">
- <channel>
- <title>The SimpleBlog</title>
- <link>http://jaspervdj.be/</link>
- <description>Simple blog in hakyll</description>
- $items
- </channel>
-</rss>
-~~~~~
+After we created a simple brochure site, we're going to try something more
+advanced: we are going to create a simple blog system.
-We thus render our feed with the following code (no, I didn't define `rssPage`
-yet - I'm going to work from bottom to top here, it's easier to explain it
-that way).
+A [zip file containing the source](examples/simpleblog.zip) for this
+tutorial is also available.
+
+Blogs, as you probably know, are composed of posts. In Hakyll, we're going
+to use simple pages for posts. All posts are located in the `posts`
+directory. But we're not going to use the `directory` command here - you will
+see why later. First, some trivial things like css.
~~~~~{.haskell}
-renderChain ["templates/rss.xml"] rssPage
+main = hakyll $ do
+ directory css "css"
~~~~~
-This, as you can see, is a regular render chain, once again. We need make a
-`Renderable` that "fills in" the `$items` identifier. We're going to do this
-using a [custom page](tutorial2.html#custom-pages).
+## Finding the posts, and a bit about renderables
+
+`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will
+provide us with all the blog posts. The blog posts have a
+`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we
+can sort them easily, you could of course name them whatever you want. They
+contain some metadata, too:
-## Custom pages again
+ 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...
-Note that we do not have to include all posts in the rss feed - only a few
-recent ones. We'll settle on the latest three here.
+Now, we find the posts and sort them reversed:
+
+~~~~~{.haskell}
+-- Find all post paths.
+postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts"
+~~~~~
-We want to render every post using the following template,
-`templates/rssitem.xml`:
+Our `postPaths` value is now of the type `[FilePath]`. `FilePath` is no
+instance of `Renderable`, but `PagePath` is:
~~~~~{.haskell}
-<item>
- <title>$title</title>
- <link>http://example.com/$url</link>
- <description>$title by $author</description>
-</item>
+let renderablePosts = map createPagePath postPaths
~~~~~
-Since we build on the previous example, we still have our `renderablePosts`
-list. We'll be using it again:
+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}
-let recentRSSItems = renderAndConcat "templates/rssitem.xml"
- (take 3 renderablePosts)
+mapM_ (renderChain [ "templates/post.html"
+ , "templates/default.html"
+ ]) renderablePosts
~~~~~
-We're using the `renderAndConcat` function again. Note that because of
-hakyll/haskell laziness, this action isn't executed directly, and this helps
-dependency handling.
+Remember that the `renderChain` works by rendering the datatype using the first
+template, creating a new page with the render result in the `body` field, and so
+on until it has been rendered with all templates.
-Now, the `rssPage` page. As you might remember, we use the `createCustomPage`
-function to create a custom page. We first give the destination url, then a
-list of dependencies, and then a list of `(key, value)` pairs.
+Now, we have the posts rendered. What is left is to generate some kind of index
+page with links to those posts. We want one general list showing all posts, and
+we want to show a few recent posts on the index page.
+
+## Custom Pages
+
+Currently, there are 3 renderable datatypes in Hakyll:
+
+- `Page`: The result of any rendering action. It is generally not recommended
+ to use pages a lot, because they cannot check dependencies (and therefore,
+ you would always regenerate your entire site if you use pages the wrong way).
+- `PagePath`: Basically just a `FilePath` in a box. Internally, this will use
+ a `Page` for rendering, but `PagePath` provides better dependency checking
+ and works on a higher level.
+- `CustomPage`: Basically the name says it - the preferred way of creating
+ custom pages in Hakyll.
+
+We will use a `CustomPage` here. Basically, all `Renderable` datatypes are in
+the end just `key: value` mappings. A CustomPage is created using the
+`createCustomPage` function, which has the following type signature:
~~~~~{.haskell}
-let rssPage = createCustomPage
- "rss.xml"
- ("templates/postitem.html" : take 3 postPaths)
- [("items", Right recentRSSItems)]
+createCustomPage :: FilePath
+ -> [FilePath]
+ -> [(String, Either String (IO String)]
~~~~~
-## Adding a link to the feed
+The first argument is the `url` of the page to generate. For our index page,
+this will be, `index.html`. The second argument is _a list of dependencies_.
+Basically, you should here give a list of files on which your custom page
+depends.
-According to the w3 organization,
-[you should add \<link\> tags](http://www.w3.org/QA/Tips/use-links) to your
-documents, so we'll do this. In `templates/default.html`:
+The last argument is obviously our `key: value` mapping. But why the `Either`?
+This, once again, is about dependency handling. The idea is that you can choose
+which type to use for the value:
-~~~~~~{.html}
-<head>
- <title>SimpleBlog - $title</title>
- <link rel="stylesheet" type="text/css" href="$$root/css/default.css" />
- <link rel="alternate"
- type="application/rss+xml"
- title="SimpleBlog"
- href="http://example.com/rss.xml" />
-</head>
-~~~~~~
+- `String`: Simply a `String`.
+- `IO String`: Here, you can give an arbitrary `IO` action that will result
+ in a String. However - this action _will not be executed_ when the file
+ in `_site` is up-to-date.
-This makes most browsers see the rss feed, and show it in the address bar.
-If you want, you can also add a pretty button to your blog linking to
-`rss.xml`.
+First, let us define this `IO String` for our index page. We want to render
+every post using a simple template:
-## That's it!
+~~~~~{.html}
+<li>
+ <a href="$$root/$url">$title</a>
+ - <em>$date</em> - by <em>$author</em>
+</li>
+~~~~~
+
+When every post is rendered with this template, we then want to concatenate the
+result. Since rendering and concatenating is pretty common, Hakyll provides us
+with a high-level function to do this.
-Yep, that's it. Feel free to play with the source code in
-[the zip file](examples/rssblog.zip) and extend the blog further. As always,
-all questions are welcome on the
+~~~~~{.haskell}
+let recentPosts = renderAndConcat ["templates/postitem.html"]
+ (take 3 renderablePosts)
+~~~~~
+
+Now, creating our custom page is fairly straight-forward:
+
+~~~~~{.haskell}
+createCustomPage "index.html"
+ ("templates/postitem.html" : take 3 postPaths)
+ [ ("title", Left "All posts")
+ , ("posts", Right recentPosts)
+ ]
+~~~~~
+
+You can see our three arguments here. We're rendering `index.html`, then we tell
+Hakyll on what files it depends - here the `templates/postitem.html` template
+and the latest 3 posts. Finally, we give a `title` value to substitute in the
+template, and the result of our concatenation. Of course, we also need to render
+this custom page:
+
+~~~~~{.haskell}
+renderChain ["index.html", "templates/default.html"] $
+ createCustomPage "index.html"
+ ("templates/postitem.html" : take 3 postPaths)
+ [ ("title", Left "All posts")
+ , ("posts", Right recentPosts)
+ ]
+~~~~~
+
+Note that the `index.html` in the `renderChain` list is also a template.
+
+## That's that
+
+If you have any more questions, feel free to ask them on the
[google discussion group](http://groups.google.com/group/hakyll).
+
+There is a [next tutorial](tutorial4.html), explaining how to add an RSS feed
+to our sample blog.
diff --git a/examples/hakyll/tutorial4.markdown b/examples/hakyll/tutorial4.markdown
index d1d6366..a53e795 100644
--- a/examples/hakyll/tutorial4.markdown
+++ b/examples/hakyll/tutorial4.markdown
@@ -1,211 +1,124 @@
---
title: Tutorial (Part IV)
-what: adds tags and context manipulations to our blog
+what: adds an RSS feed to the blog from the previous tutorial
---
-Here, have [a zip file](examples/tagblog.zip) for this tutorial.
-
-## Context manipulations
-
-As you might remember, `Renderable` objects are usually just key-value mappings.
-We can render those with templates, and then the `$key`'s in the template get
-substituted by the appropriate values. This is a rather flexible system, but
-there are limitations. Some of these limitations can be solved using
-_Context Manipulations_.
-
-In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
-
-~~~~~{.haskell}
-type ContextManipulation = Context -> Context
-~~~~~
-
-Where `Context` is a key-value mapping. Usually, you don't create
-`ContextManipulation`'s by hand, but you use the `renderValue` function. Let's
-have a look at it's type.
-
-~~~~~{.haskell}
-renderValue :: String -> String
- -> (String -> String)
- -> ContextManipulation
-~~~~~
-
-This is the preferred way of creating context manipulations. The first argument
-is the `key` to manipulate. The second argument is the `key` where the new value
-should be placed. If this is the same as the first argument, it will be
-replaced. The third argument is the function to manipulate the `value` with.
-
-As a simple example, let's write a function that puts the `$title` in uppercase.
-
-~~~~~{.haskell}
-import Data.Char (toUpper)
-
-titleUpper :: ContextManipulation
-titleUpper = renderValue "title" "title" $ map toUpper
-~~~~~
-
-## Applying Context Manipulations
-
-Now, the question is how to apply these `ContextManipulation`'s. The answer is
-simple. For every important render function (`render`, `renderChain`,
-`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
-first argument. These functions are thus: `renderWith`, `renderChainWith`,
-`renderAndConcatWith`. In fact, the following holds true:
-
-~~~~~{.haskell}
-render == renderWith id
-renderChain == renderChainWith id
-renderAndConcat == renderAndConcatWith id
+## Adding RSS to our simple blog
+
+In this tutorial, we're going to add an RSS feed to the blog we wrote in
+[the previous tutorial](tutorial2.html). Here is a
+[zip file containing the source](examples/rssblog.zip).
+
+An RSS feed looks like this:
+
+~~~~~{.xml}
+<?xml version="1.0" ?>
+<rss version="2.0">
+ <channel>
+ <title>The SimpleBlog</title>
+ <link>http://example.com/</link>
+ <description>Simple blog in hakyll</description>
+ <item>
+ <title>Title goes here</title>
+ <link>http://example.com/post.html</link>
+ <description>
+ A description is optional.
+ </description>
+ </item>
+ </channel>
+</rss>
~~~~~
-So we could use or title manipulation like this:
-
-~~~~~{.haskell}
-renderChainWith titleUpper ["templates/default.html"]
- (createPagePath "index.markdown")
+Note that, obviously, there can be more than one item. We're going to use a
+template to render this. This is where `templates/rss.xml` comes in:
+
+~~~~~{.xml}
+<?xml version="1.0" ?>
+<rss version="2.0">
+ <channel>
+ <title>The SimpleBlog</title>
+ <link>http://jaspervdj.be/</link>
+ <description>Simple blog in hakyll</description>
+ $items
+ </channel>
+</rss>
~~~~~
-## 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`.
+We thus render our feed with the following code (no, I didn't define `rssPage`
+yet - I'm going to work from bottom to top here, it's easier to explain it
+that way).
~~~~~{.haskell}
-postManipulation :: ContextManipulation
-postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
-~~~~~
-
-That manipulation will:
-- Read the date from the file name the post was loaded from.
-- Parse the date and render it in a `%B %e, %Y` format. This is a
- `Month day, Year` format.
-- Put the result in the `date` metadata field.
-- If the date could not be parsed, it will put `"Unknown date"` in the `date`
- metadata field.
-
-So, we can throw away our `date: ` lines from our posts, and still use `$date`
-in our templates.
-
-## Abstracting the post list
-
-Now, we're going to render tags. This is also done using context manipulations.
-Hakyll has a specialized module to deal with tags, provided by
-`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
-the `tags` metadata field.
-
-> ---
-> title: A third post
-> author: Publius Ovidius Naso
-> tags: epic fail, ovidius
-> ---
-> Pellentesque tempor blandit elit, vel...
-
-But first things first. We need to render a post list for every tag. We already
-had some code to render a list of all posts. We're just going to abstract this
-code into a more general function:
-
-~~~~{.haskell}
-renderPostList url title posts = do
- let postItems = renderAndConcatWith
- postManipulation
- "templates/postitem.html"
- (map createPagePath posts)
- customPage = createCustomPage
- url
- ("templates/postitem.html" : posts)
- [ ("title", Left title)
- , ("posts", Right postItems)
- ]
- renderChain ["posts.html", "templates/default.html"]
- customPage
+renderChain ["templates/rss.xml"] rssPage
~~~~~
-Our "render all posts" action can now be written as:
+This, as you can see, is a regular render chain, once again. We need make a
+`Renderable` that "fills in" the `$items` identifier. We're going to do this
+using a [custom page](tutorial2.html#custom-pages).
-~~~~~{.haskell}
-renderPostList "posts.html" "All posts" postPaths
-~~~~~
+## Custom pages again
-## Tag links
+Note that we do not have to include all posts in the rss feed - only a few
+recent ones. We'll settle on the latest three here.
-We want to 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:
+We want to render every post using the following template,
+`templates/rssitem.xml`:
~~~~~{.haskell}
-tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
+<item>
+ <title>$title</title>
+ <link>http://example.com/$url</link>
+ <description>$title by $author</description>
+</item>
~~~~~
-`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`.
+Since we build on the previous example, we still have our `renderablePosts`
+list. We'll be using it again:
~~~~~{.haskell}
-postManipulation :: ContextManipulation
-postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
- . renderTagLinks tagToURL
+let recentRSSItems = renderAndConcat ["templates/rssitem.xml"]
+ (take 3 renderablePosts)
~~~~~
-If we click a tag, we get a `404`. That's because we haven't generated the
-post lists for every tag.
+We're using the `renderAndConcat` function again. Note that because of
+hakyll/haskell laziness, this action isn't executed directly, and this helps
+dependency handling.
-## The Tag Map
-
-Hakyll provides a function called `readTagMap`. Let's inspect it's type.
+Now, the `rssPage` page. As you might remember, we use the `createCustomPage`
+function to create a custom page. We first give the destination url, then a
+list of dependencies, and then a list of `(key, value)` pairs.
~~~~~{.haskell}
-readTagMap [FilePath] -> IO Map String [FilePath]
+let rssPage = createCustomPage
+ "rss.xml"
+ ("templates/postitem.html" : take 3 postPaths)
+ [("items", Right recentRSSItems)]
~~~~~
-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.
+## Adding a link to the feed
-~~~~~{.haskell}
-tagMap <- readTagMap postPaths
-let renderListForTag (tag, posts) =
- renderPostList (tagToURL tag)
- ("Posts tagged " ++ tag)
-mapM_ renderListForTag (toList tagMap)
-~~~~~
-
-There we go. We now have clickable tags, and a post list for every tag.
-
-## A Tag Cloud
+According to the w3 organization,
+[you should add \<link\> tags](http://www.w3.org/QA/Tips/use-links) to your
+documents, so we'll do this. In `templates/default.html`:
-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.
+~~~~~~{.html}
+<head>
+ <title>SimpleBlog - $title</title>
+ <link rel="stylesheet" type="text/css" href="$$root/css/default.css" />
+ <link rel="alternate"
+ type="application/rss+xml"
+ title="SimpleBlog"
+ href="http://example.com/rss.xml" />
+</head>
+~~~~~~
-~~~~~{.haskell}
-TagCloud :: M.Map String [FilePath]
- -> (String -> String)
- -> Float
- -> Float
- -> String
-~~~~~
-
-The first argument is obviously the result of the `readTagMap` function. The
-second argument is, once again, a function to create an url for a given tag.
-Then, we give a minimum and a maximum font size in percent, and we get the
-tag cloud back. We can add this to our index:
-
-~~~~~{.haskell}
-let tagCloud = renderTagCloud tagMap tagToURL 100 200
-...
-createCustomPage "index.html"
- ("templates/postitem.html" : take 3 postPaths)
- [ ("title", Left "Home")
- , ("posts", Right recentPosts)
- , ("tagcloud", Left tagCloud)
- ]
-~~~~~
+This makes most browsers see the rss feed, and show it in the address bar.
+If you want, you can also add a pretty button to your blog linking to
+`rss.xml`.
-## That's it
+## That's it!
-Feel free to hack around with the code from the zip file. As always, if you
-still have questions, ask them at the
+Yep, that's it. Feel free to play with the source code in
+[the zip file](examples/rssblog.zip) and extend the blog further. As always,
+all questions are welcome on the
[google discussion group](http://groups.google.com/group/hakyll).
diff --git a/examples/hakyll/tutorial5.markdown b/examples/hakyll/tutorial5.markdown
new file mode 100644
index 0000000..84f0dee
--- /dev/null
+++ b/examples/hakyll/tutorial5.markdown
@@ -0,0 +1,211 @@
+---
+title: Tutorial (Part V)
+what: adds tags and context manipulations to our blog
+---
+
+Here, have [a zip file](examples/tagblog.zip) for this tutorial.
+
+## Context manipulations
+
+As you might remember, `Renderable` objects are usually just key-value mappings.
+We can render those with templates, and then the `$key`'s in the template get
+substituted by the appropriate values. This is a rather flexible system, but
+there are limitations. Some of these limitations can be solved using
+_Context Manipulations_.
+
+In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
+
+~~~~~{.haskell}
+type ContextManipulation = Context -> Context
+~~~~~
+
+Where `Context` is a key-value mapping. Usually, you don't create
+`ContextManipulation`'s by hand, but you use the `renderValue` function. Let's
+have a look at it's type.
+
+~~~~~{.haskell}
+renderValue :: String -> String
+ -> (String -> String)
+ -> ContextManipulation
+~~~~~
+
+This is the preferred way of creating context manipulations. The first argument
+is the `key` to manipulate. The second argument is the `key` where the new value
+should be placed. If this is the same as the first argument, it will be
+replaced. The third argument is the function to manipulate the `value` with.
+
+As a simple example, let's write a function that puts the `$title` in uppercase.
+
+~~~~~{.haskell}
+import Data.Char (toUpper)
+
+titleUpper :: ContextManipulation
+titleUpper = renderValue "title" "title" $ map toUpper
+~~~~~
+
+## Applying Context Manipulations
+
+Now, the question is how to apply these `ContextManipulation`'s. The answer is
+simple. For every important render function (`render`, `renderChain`,
+`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
+first argument. These functions are thus: `renderWith`, `renderChainWith`,
+`renderAndConcatWith`. In fact, the following holds true:
+
+~~~~~{.haskell}
+render == renderWith id
+renderChain == renderChainWith id
+renderAndConcat == renderAndConcatWith id
+~~~~~
+
+So we could use or title manipulation like this:
+
+~~~~~{.haskell}
+renderChainWith titleUpper ["templates/default.html"]
+ (createPagePath "index.markdown")
+~~~~~
+
+## Rendering dates
+
+As you remember, in our previous blog, all posts had a file name like
+`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
+had a metadata field `date`, containing a human-readable date. This is not very
+D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
+dates encoded in paths: `renderDate`.
+
+~~~~~{.haskell}
+postManipulation :: ContextManipulation
+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
+~~~~~
+
+That manipulation will:
+- Read the date from the file name the post was loaded from.
+- Parse the date and render it in a `%B %e, %Y` format. This is a
+ `Month day, Year` format.
+- Put the result in the `date` metadata field.
+- If the date could not be parsed, it will put `"Unknown date"` in the `date`
+ metadata field.
+
+So, we can throw away our `date: ` lines from our posts, and still use `$date`
+in our templates.
+
+## Abstracting the post list
+
+Now, we're going to render tags. This is also done using context manipulations.
+Hakyll has a specialized module to deal with tags, provided by
+`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
+the `tags` metadata field.
+
+ ---
+ title: A third post
+ author: Publius Ovidius Naso
+ tags: epic fail, ovidius
+ ---
+ Pellentesque tempor blandit elit, vel...
+
+But first things first. We need to render a post list for every tag. We already
+had some code to render a list of all posts. We're just going to abstract this
+code into a more general function:
+
+~~~~{.haskell}
+renderPostList url title posts = do
+ let postItems = renderAndConcatWith
+ postManipulation
+ "templates/postitem.html"
+ (map createPagePath posts)
+ customPage = createCustomPage
+ url
+ ("templates/postitem.html" : posts)
+ [ ("title", Left title)
+ , ("posts", Right postItems)
+ ]
+ renderChain ["posts.html", "templates/default.html"]
+ customPage
+~~~~~
+
+Our "render all posts" action can now be written as:
+
+~~~~~{.haskell}
+renderPostList "posts.html" "All posts" postPaths
+~~~~~
+
+## Tag links
+
+We want to display the tags for our post under the title. But if we use the
+`$tags` key in a template, we will just have the plain tags - they will not be
+clickable. We can again solve this with a `ContextManipulation`. We have a
+function that produces an url for a given tag:
+
+~~~~~{.haskell}
+tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
+~~~~~
+
+`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
+a specialized `renderValue` function for creating linked tags called
+`renderTagLinks`. This function simply takes a function that produces an url
+for a given tag - the function we just wrote. Let's extend our
+`postManipulation`.
+
+~~~~~{.haskell}
+postManipulation :: ContextManipulation
+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
+ . renderTagLinks tagToURL
+~~~~~
+
+If we click a tag, we get a `404`. That's because we haven't generated the
+post lists for every tag.
+
+## The Tag Map
+
+Hakyll provides a function called `readTagMap`. Let's inspect it's type.
+
+~~~~~{.haskell}
+readTagMap [FilePath] -> IO Map String [FilePath]
+~~~~~
+
+You give it a list of paths, and it creates a map that, for every tag, holds
+a number of posts. We can easily use this to render a post list for every tag.
+
+~~~~~{.haskell}
+tagMap <- readTagMap postPaths
+let renderListForTag (tag, posts) =
+ renderPostList (tagToURL tag)
+ ("Posts tagged " ++ tag)
+mapM_ renderListForTag (toList tagMap)
+~~~~~
+
+There we go. We now have clickable tags, and a post list for every tag.
+
+## A Tag Cloud
+
+A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
+generate a tag cloud. Let's have a look at the `renderTagCloud` function.
+
+~~~~~{.haskell}
+TagCloud :: M.Map String [FilePath]
+ -> (String -> String)
+ -> Float
+ -> Float
+ -> String
+~~~~~
+
+The first argument is obviously the result of the `readTagMap` function. The
+second argument is, once again, a function to create an url for a given tag.
+Then, we give a minimum and a maximum font size in percent, and we get the
+tag cloud back. We can add this to our index:
+
+~~~~~{.haskell}
+let tagCloud = renderTagCloud tagMap tagToURL 100 200
+...
+createCustomPage "index.html"
+ ("templates/postitem.html" : take 3 postPaths)
+ [ ("title", Left "Home")
+ , ("posts", Right recentPosts)
+ , ("tagcloud", Left tagCloud)
+ ]
+~~~~~
+
+## That's it
+
+Feel free to hack around with the code from the zip file. As always, if you
+still have questions, ask them at the
+[google discussion group](http://groups.google.com/group/hakyll).
diff --git a/examples/morepages/about.markdown b/examples/morepages/about.markdown
new file mode 100644
index 0000000..59d1aca
--- /dev/null
+++ b/examples/morepages/about.markdown
@@ -0,0 +1,27 @@
+---
+title: About
+
+--- section1
+## Mattis
+Nullam imperdiet sodales orci vitae molestie. Nunc quam orci, pharetra a
+rhoncus vitae, eleifend id felis. Suspendisse potenti. Etiam vitae urna orci.
+Quisque pellentesque dignissim felis, egestas tempus urna luctus vitae. In hac
+habitasse platea dictumst. Morbi fringilla mattis odio, et mattis tellus
+accumsan vitae.
+
+--- section2
+## Orci
+Vivamus eget mauris sit amet nulla laoreet lobortis. Nulla in diam elementum
+risus convallis commodo. Cras vehicula varius dui vitae facilisis. Proin
+elementum libero eget leo aliquet quis euismod orci vestibulum. Duis rhoncus
+lorem consequat tellus vestibulum aliquam. Quisque orci orci, malesuada porta
+blandit et, interdum nec magna.
+
+--- section3
+## Augue
+In urna ante, pulvinar et imperdiet nec, fermentum ac tortor. Cras tristique
+pellentesque euismod. Pellentesque est ante, sagittis vitae vehicula vitae,
+ullamcorper eget lectus. Curabitur egestas accumsan leo, ac ullamcorper nibh
+tincidunt id. Curabitur lorem libero, fermentum non tincidunt ac, pretium in
+libero. Donec vel mi eu tortor accumsan dictum ut in augue. Vestibulum est
+lorem, bibendum eu vehicula eu, convallis eget mauris.
diff --git a/examples/morepages/css/default.css b/examples/morepages/css/default.css
new file mode 100644
index 0000000..0376cae
--- /dev/null
+++ b/examples/morepages/css/default.css
@@ -0,0 +1,27 @@
+body {
+ width: 600px;
+ margin: 0px auto 0px auto;
+}
+
+div#navigation {
+ text-align: center;
+ border-bottom: 4px solid black;
+}
+
+div#navigation a {
+ color: white;
+ text-decoration: none;
+ background-color: black;
+ padding: 3px 10px 3px 10px;
+ margin: 0px 10px 0px 10px;
+}
+
+div.column {
+ width: 30%;
+ float: left;
+ margin: 0px 10px 0px 10px;
+}
+
+div#footer {
+ clear: both;
+}
diff --git a/examples/morepages/footer.markdown b/examples/morepages/footer.markdown
new file mode 100644
index 0000000..408541c
--- /dev/null
+++ b/examples/morepages/footer.markdown
@@ -0,0 +1,16 @@
+--- footer
+## Sapien
+
+In hac habitasse platea dictumst. Cras placerat felis nec risus varius in
+accumsan sem fermentum. Vestibulum elementum aliquam tortor semper vulputate.
+Vivamus tincidunt tellus sed purus tempor fringilla. Morbi dui nisl, eleifend
+non dictum vitae, luctus eu lacus. Duis vitae lacus sem, ut porta mauris.
+Aenean sed ultricies dui. Vivamus ullamcorper metus lorem, at ornare nibh.
+Mauris mi metus, convallis id lobortis vitae, interdum quis felis. Cras elit
+massa, pellentesque sit amet pharetra ut, volutpat in arcu. Vivamus blandit,
+ligula et ultricies consequat, metus sem congue quam, ac pretium enim velit at
+tortor. Cras in tellus eu sapien pulvinar sollicitudin eu id ipsum. Mauris nec
+urna tellus, et scelerisque tellus. Nunc imperdiet felis nec libero consectetur
+tristique tristique ipsum sodales. Cras tortor nisl, condimentum in
+pellentesque id, interdum vel mi. Suspendisse auctor vehicula orci at
+scelerisque. Vivamus quis sagittis felis.
diff --git a/examples/morepages/hakyll.hs b/examples/morepages/hakyll.hs
new file mode 100644
index 0000000..a806042
--- /dev/null
+++ b/examples/morepages/hakyll.hs
@@ -0,0 +1,13 @@
+import Text.Hakyll (hakyll, defaultHakyllConfiguration)
+import Text.Hakyll.File (directory)
+import Text.Hakyll.Render (css, static, renderChain)
+import Text.Hakyll.Renderables (createPagePath, combine)
+
+main = hakyll defaultHakyllConfiguration $ do
+ directory css "css"
+ render "about.markdown"
+ render "index.markdown"
+ render "products.markdown"
+ where
+ render = renderChain ["templates/default.html"] . withFooter . createPagePath
+ withFooter a = a `combine` createPagePath "footer.markdown"
diff --git a/examples/morepages/index.markdown b/examples/morepages/index.markdown
new file mode 100644
index 0000000..4a2dc80
--- /dev/null
+++ b/examples/morepages/index.markdown
@@ -0,0 +1,24 @@
+---
+title: Home
+
+--- section1
+## Purus
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempor, urna et
+auctor tincidunt, eros mauris facilisis purus, eget sollicitudin leo massa sit
+amet ipsum. Vivamus eu massa in urna vehicula rutrum eget sit amet purus.
+
+--- section2
+## Ligula
+Mauris sit amet justo mi. Curabitur vel quam felis. In hac habitasse platea
+dictumst. Etiam nec consequat risus. Donec consequat est vitae neque fermentum
+feugiat nec ac nibh. Nulla rhoncus, odio quis scelerisque rutrum, metus sem
+tempor ante, a ornare ipsum felis sed ligula. Morbi urna lectus, scelerisque
+non pharetra in, rutrum quis ligula. Phasellus semper ullamcorper arcu eu
+auctor.
+
+--- section3
+## Justo
+Aliquam sagittis tincidunt libero ut elementum. Ut sit amet vestibulum metus.
+Ut aliquet congue neque eu tincidunt. Integer eu elit sed massa sollicitudin
+vehicula nec ut sem. Cras euismod enim eget purus lacinia non feugiat urna
+imperdiet. Aliquam justo sem, viverra eu vehicula vitae, imperdiet vel magna.
diff --git a/examples/morepages/products.markdown b/examples/morepages/products.markdown
new file mode 100644
index 0000000..7032479
--- /dev/null
+++ b/examples/morepages/products.markdown
@@ -0,0 +1,23 @@
+---
+title: Products
+
+--- section1
+## Lacus
+Etiam condimentum auctor semper. Donec lobortis, magna id sodales sollicitudin,
+lectus mi egestas nulla, pulvinar lobortis nunc eros id nisl. Curabitur
+imperdiet, erat at accumsan vulputate, purus nunc blandit nulla, dictum
+vestibulum sem lorem eget ipsum.
+
+--- section2
+## Vitae
+Integer ut dui eu felis mollis vestibulum. Etiam at nibh id diam aliquet
+vestibulum sit amet a nibh. Aliquam erat volutpat. Etiam vitae nulla at dolor
+fringilla tempor ut a nunc. Pellentesque elementum elit lorem. Quisque nec
+ligula ipsum. Nunc augue lacus, ullamcorper vel dapibus in, mattis eget elit.
+
+--- section3
+## Feugiat
+Pellentesque enim dui, interdum elementum vehicula luctus, feugiat vitae arcu.
+Vestibulum ut felis justo. Quisque vestibulum mauris eget ipsum luctus
+consequat. Nunc tincidunt, turpis ut fermentum dapibus,
+justo tortor bibendum sem, at facilisis justo odio luctus lectus.
diff --git a/examples/morepages/templates/default.html b/examples/morepages/templates/default.html
new file mode 100644
index 0000000..ef3e03c
--- /dev/null
+++ b/examples/morepages/templates/default.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>MyAweSomeCompany - $title</title>
+ <link rel="stylesheet" type="text/css" href="$root/css/default.css" />
+ </head>
+ <body>
+ <h1>MyAweSomeCompany - $title</h1>
+ <div id="navigation">
+ <a href="$root/index.html">Home</a>
+ <a href="$root/about.html">About</a>
+ <a href="$root/products.html">Products</a>
+ </div>
+
+ <div class="column"> $section1 </div>
+ <div class="column"> $section2 </div>
+ <div class="column"> $section3 </div>
+ <div id="footer"> $footer </div>
+ </body>
+</html>