diff options
-rw-r--r-- | examples/hakyll/css/default.css | 4 | ||||
-rw-r--r-- | examples/hakyll/tutorial1.markdown | 215 | ||||
-rw-r--r-- | examples/hakyll/tutorial2.markdown | 161 | ||||
-rw-r--r-- | examples/hakyll/tutorial3.markdown | 170 | ||||
-rw-r--r-- | examples/hakyll/tutorial4.markdown | 114 | ||||
-rw-r--r-- | examples/hakyll/tutorial5.markdown | 209 | ||||
-rw-r--r-- | examples/hakyll/tutorial6.markdown | 88 | ||||
-rw-r--r-- | examples/hakyll/tutorial7.markdown | 51 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part07.markdown | 2 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part08.markdown | 7 |
10 files changed, 12 insertions, 1009 deletions
diff --git a/examples/hakyll/css/default.css b/examples/hakyll/css/default.css index 41dc637..0e9462c 100644 --- a/examples/hakyll/css/default.css +++ b/examples/hakyll/css/default.css @@ -108,3 +108,7 @@ pre code { padding: 8px; margin-bottom: 2em; } + +p.caption { + display: none; +} diff --git a/examples/hakyll/tutorial1.markdown b/examples/hakyll/tutorial1.markdown deleted file mode 100644 index d4ef575..0000000 --- a/examples/hakyll/tutorial1.markdown +++ /dev/null @@ -1,215 +0,0 @@ ---- -title: Brochure -what: explains how to create a simple brochure site ---- - -## Getting started - -First, make sure you have Hakyll installed. The recommended way to do this is -through [hackage](http://hackage.haskell.org/) using -[cabal-install](http://www.haskell.org/haskellwiki/Cabal-Install). This -tutorial also assumes you have a basic knowledge of Haskell. - -~~~~~ -[jasper@alice ~]$ cabal install hakyll -~~~~~ - -## Building a simple static site - -As an example to get started with, we're going to develop a so called -"Brochure Site" for an imaginary company. The first step is to create -a directory for our new site. - -~~~~~ -[jasper@alice Sites]$ mkdir brochure -[jasper@alice Sites]$ cd brochure/ -[jasper@alice brochure]$ -~~~~~ - -I have a [zip file](examples/brochure.zip) with the files we need for this -tutorial available. Please unzip it in the brochure directory we just created. -We'll first have a look at what we're going to create (because we're curious -and all that). - -~~~~~ -[jasper@alice brochure]$ ghc --make hakyll.hs -[1 of 1] Compiling Main ( hakyll.hs, hakyll.o ) -Linking hakyll ... -[jasper@alice brochure]$ ./hakyll preview -Generating... -Starting hakyll server on port 8000... -~~~~~ - -If you now point your browser at [localhost:8000](http://localhost:8000/) you -should see our simple brochure site. - -## hakyll.hs - -The main configuration file of a Hakyll site is traditionally called -`hakyll.hs`. It is nothing special, just a small Haskell program. There is no -magic going on. - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -main = hakyll $ do - putStrLn "I'm in your computer, generating your site!" -~~~~~ - -Note how we wrap everyting in the `hakyll` function. This is useful because -it will generate a very nice main function. - -## Pages - -An important concept in Hakyll is Pages. Pages are text files that can be -written in markdown, html, rst... basically anything Pandoc supports. -Furthermore, they can also contain some 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 `index.markdown` page. - - --- - 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 -markdown, I think [this](http://daringfireball.net/projects/markdown/syntax) -is a pretty good page. - -## Templates - -Another concept are the so-called templates. Templates are text files (usually -html files) containing a number of keys. The syntax for these keys is -`$identifier`. Our example site contains one template, namely -`templates/default.html`. Let's have a better look at that. - -~~~~~{.html} -<html> - <head> - <title>MyAweSomeCompany - $title</title> - <link rel="stylesheet" type="text/css" - href="$$root/css/default.css" /> - <link rel="stylesheet" type="text/css" - href="$$root/css/syntax.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/code.html">Code</a> - </div> - - $body - </body> -</html> -~~~~~ - -We can see how our `Page` would fit in. When we render the page we saw using -this template, `$title` would be replaced by `About`, and `$body` would be -replaced by the body of the about page. `body` is the traditional name for the -body of any page - that is the convention in Hakyll. Also note that in this -case, `$body` would be replaced by a chunk of html - the result of the -markdown-to-html conversion. - -## The $$root key - -There is one "special" key in Hakyll: the $$root key. What is so special about -it? Well, internally, it is treated differently - but this should not concern -you. The thing is that it is the only key you can also use in **Pages**. - -It will be substituted by a relative url part (like `..` or `../..`) so it -points to the root directory of your site. It is recommended to use this -whenever you need it, it can save you some time from messing with absolute -and relative URL's. - -## Putting it all together - -Now, we'll render the page using the `renderChain` function. This function -takes a list of templates and a renderable object. In our case, we only have -one template, and our renderable object is simply a `PagePath`. - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -import Text.Hakyll.Render (renderChain) -import Text.Hakyll.Renderables (createPagePath) -main = hakyll $ do - renderChain ["templates/default.html"] - (createPagePath "index.markdown") -~~~~~ - -Or, to render all our three pages: - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -import Text.Hakyll.Render (renderChain) -import Text.Hakyll.Renderables (createPagePath) -main = hakyll $ do - render "about.rst" - render "index.markdown" - render "code.lhs" - where render = renderChain ["templates/default.html"] - . createPagePath -~~~~~ - -As you can see, we can render a variety of formats. This will create the -following files: - -~~~~~ -_site/about.html -_site/index.html -_site/code.html -~~~~~ - -## CSS, images and other static files - -Now, we also have a css file we would like to have in the `_site` directory. -Static files can be rendered using the `static` function in Hakyll. We could -use: - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -import Text.Hakyll.Render (static) -main = hakyll $ do - static "css/default.css" -~~~~~ - -This would work, but let's not forget that Hakyll also has css compression. If -we want to use that, we would use `css` instead of `static`. - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -import Text.Hakyll.Render (css) -main = hakyll $ do - css "css/default.css" -~~~~~ - -If we were to create another css file, we would have to add a line to our -`hakyll.hs` configuration file. This is pretty stupid, because the whole -directory `css` contains only css files. That's why Hakyll has a `directory` -function, which will execute a given function on an entire directory. So, -our example would become: - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -import Text.Hakyll.Render (css) -import Text.Hakyll.File (directory) -main = hakyll $ do - directory css "css" -~~~~~ - -## Deploying - -To setup your site, simply copy the contents of `_site` to your hosting provider -using your favorite piece of software. - -## That's it! - -Now you should fully understand our brochure site example. If you still have -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: -[more about writing pages](tutorial2.html). diff --git a/examples/hakyll/tutorial2.markdown b/examples/hakyll/tutorial2.markdown deleted file mode 100644 index 87dc3a0..0000000 --- a/examples/hakyll/tutorial2.markdown +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: MorePages -what: elaborates a little on writing pages and templates ---- - -## The structure of a Page - -The most important thing to realize is that a `Page` is just a key-value -mapping. Another example: - - --- - title: About - author: Mia Wallace - --- - Hello there! This is - a simple about page. - -This will produce the following mapping: - -- `title`: About -- `author`: Mia Wallace -- `body`: Hello there! This is a simple about page. - -`body` is the traditional name for the main body part of a page. If the page has -a `.markdown` extension for example, this would also be rendered by pandoc. But -pages are more flexible. The following is also a valid page: - - Hello there! This is - a simple about page. - -This will produce one key-value pair: - -- `body`: Hello there! This is a simple about page. - -But the `Page` parser can do more than this. You can add extra sections, apart -from the body, and even leave out the body. - - --- - author: Vincent Vega - - --- prelude - A small introduction goes here. I can write *markdown* - here, by the way. Well, assuming this page has a - `.markdown` extension. - - --- main - I can write some more things here. - -This will produce the following: - -- `author`: Vincent Vega -- `prelude`: A small introduction goes here. I can write *markdown* here, by the - way. Well, assuming this page has a `.markdown` extension. -- `main`: I can write some more things here. - -The example from this tutorial (we will see later) uses this to build a -three-column system for the website, separating content from layout. - -## Combining pages - -Now you know that `Page`s, and `Renderable`s in general, are basically nothing -more than key-values mappings, it is time to abuse this fact. There is another -`Renderable` type we haven't talked about before: a `CombinedRenderable`. - -The type signature of the `combine` function does a pretty good job at -explaining it: - -~~~~~{.haskell} -combine :: (Renderable a, Renderable b) - => a -> b -> CombinedRenderable a b -~~~~~ - -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} -combineWithUrl :: (Renderable a, Renderable b) - => FilePath -> a -> b -> CombinedRenderable a b -~~~~~ - -## The example - -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. - -Every page consists of three sections, originally named `section1`, `section2` -and `section3`. So our pages look more or less like this: - - --- - title: About - - --- section1 - ## Mattis - Nullam imperdiet sodales orci vitae molestie. Nunc... - - --- section2 - ## Orci - Vivamus eget mauris sit amet nulla laoreet lobortis. - Nulla in... - - --- section3 - ## Augue - In urna ante, pulvinar et imperdiet nec, fermentum ac... - -The cool thing is we do not have to specify how these will be layed out. In our -template, we decide to use a simple three column system: - -~~~~~{.html} -<div class="column"> $section1 </div> -<div class="column"> $section2 </div> -<div class="column"> $section3 </div> -~~~~~ - -The columns are then floated using css. So far so good, but what if we wanted -an additional text block on every page? An easy solution would be to add this -to the template, but then our layout-content separation idea will be broken -again. So we simply add to the template: - -~~~~~{.html} -<div class="footer"> $footer </div> -~~~~~ - -And now we will use `combine` to put the footer on every page. We write a small -auxiliary function that combines a given `Renderable` with the footer: - -~~~~~{.haskell} -withFooter a = a `combine` createPagePath "footer.markdown" -~~~~~ - -Now, were we previously wrote: - -~~~~~{.haskell} -render "about.markdown" -where render = renderChain ["templates/default.html"] - . createPagePath -~~~~~ - -We simply have to add our footer: - -~~~~~{.haskell} -render "about.markdown" -where render = renderChain ["templates/default.html"] - . withFooter - . createPagePath -~~~~~ - -And now every page will include the footer. - -## That's all folks - -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). diff --git a/examples/hakyll/tutorial3.markdown b/examples/hakyll/tutorial3.markdown deleted file mode 100644 index a782be1..0000000 --- a/examples/hakyll/tutorial3.markdown +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: SimpleBlog -what: creates a simple blog ---- - -## Creating a simple blog with Hakyll - -After we created a simple brochure site, we're going to try something more -advanced: we are going to create a simple blog system. - -A [zip file containing the source](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} -main = hakyll $ do - directory css "css" -~~~~~ - -## 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: - - 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: - -~~~~~{.haskell} --- Find all post paths. -postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts" -~~~~~ - -Our `postPaths` value is now of the type `[FilePath]`. `FilePath` is no -instance of `Renderable`, but `PagePath` is: - -~~~~~{.haskell} -let renderablePosts = map createPagePath postPaths -~~~~~ - -We have two templates we want to render our posts with: first we would like to -render them using `templates/post.html`, and we want to render the result -using `templates/default.html`. This can be done with the `renderChain` -function: - -~~~~~{.haskell} -mapM_ (renderChain [ "templates/post.html" - , "templates/default.html" - ]) renderablePosts -~~~~~ - -Remember that the `renderChain` works by rendering the datatype using the first -template, creating a new page with the render result in the `body` field, and so -on until it has been rendered with all templates. - -Now, we have the posts rendered. What is left is to generate some kind of index -page with links to those posts. We want one general list showing all posts, and -we want to show a few recent posts on the index page. - -## Custom Pages - -Currently, there are 4 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. -- `CombinedRenderable`: A way of combining two other `Renderable`s - this is - explained in tutorial 2. - -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 (Hakyll String)] -~~~~~ - -The first argument is the `url` of the page to generate. For our index page, -this will be, `index.html`. The second argument is _a list of dependencies_. -Basically, you should here give a list of files on which your custom page -depends. - -The last argument is obviously our `key: value` mapping. But why the `Either`? -This, once again, is about dependency handling. The idea is that you can choose -which type to use for the value: - -- `String`: Simply a `String`. -- `Hakyll String`: Here, you can give an arbitrary `Hakyll` action that will - result in 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[^1]. Let's look at the signature of `createListing`: - -~~~~~{.haskell} -createListing :: (Renderable a) - -> String - -> FilePath - -> [a] - -> [(String, String)] -~~~~~ - -[^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 template to render _each renderable_ -with. We use `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 renderables. For our index, these are the 3 last posts. -The last argument of the `createListing` functions lets you specify additional -key-value pairs. 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 renderablePosts) - [("title", "Home")] -~~~~~ - -The result of this will be a `CustomPage`. The body of this page will contain -a concatenation of all the renderables, rendered with the -`templates/postitem.html` template. - -Now, we only have to render it: first using the `index.html` template, then -using the `templates/default.html` template. - -~~~~~{.haskell} -renderChain ["index.html", "templates/default.html"] index -~~~~~ - -Note that the `index.html` in the `renderChain` list is also a template. Now, -take your time to read the `index.html` template and the other files in the zip -so you understand what is going on here. - -## 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 deleted file mode 100644 index 1dfa9a0..0000000 --- a/examples/hakyll/tutorial4.markdown +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: RSSBlog -what: adds an RSS feed to the blog from the previous tutorial ---- - -## 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> -~~~~~ - -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> - $body - </channel> -</rss> -~~~~~ - -We thus render our feed with the following code (no, I didn't define `rss` -yet - I'm going to work from bottom to top here, it's easier to explain it -that way). - -~~~~~{.haskell} -renderChain ["templates/rss.xml"] rss -~~~~~ - -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). - -## Custom pages again - -Note that we do not have to include all posts in the rss feed - only a few -recent ones. We'll settle on the latest three here. - -We want to render every post using the following template, -`templates/rssitem.xml`: - -~~~~~{.haskell} -<item> - <title>$title</title> - <link>http://example.com/$url</link> - <description>$title by $author</description> -</item> -~~~~~ - -Since we build on the previous example, we still have our `renderablePosts` -list. We'll be using it again to create a listing: - -~~~~~{.haskell} -let rss = createListing "rss.xml" "templates/rssitem.xml" - (take 3 renderablePosts) [] -~~~~~ - -Note that we give `rss.xml` as destination url, because that is where we want -our `CustomPage`. We give a template to render every post with -(`"templates/rssitem.xml"`) and no additional context (so we use the empty list -`[]`). - -## Adding a link to the feed - -According to the w3 organization, -[you should add \<link\> tags](http://www.w3.org/QA/Tips/use-links) to your -documents, so we'll do this. In `templates/default.html`: - -~~~~~~{.html} -<head> - <title>SimpleBlog - $title</title> - <link rel="stylesheet" type="text/css" href="$$root/css/default.css" /> - <link rel="alternate" - type="application/rss+xml" - title="SimpleBlog" - href="$root/rss.xml" /> -</head> -~~~~~~ - -This makes most browsers see the rss feed, and show it in the address bar. -If you want, you can also add a pretty button to your blog linking to -`rss.xml`. - -## That's it! - -Yep, that's it. Feel free to play with the source code in -[the zip file](examples/rssblog.zip) and extend the blog further. As always, -all questions are welcome on the -[google discussion group](http://groups.google.com/group/hakyll). diff --git a/examples/hakyll/tutorial5.markdown b/examples/hakyll/tutorial5.markdown deleted file mode 100644 index 429a589..0000000 --- a/examples/hakyll/tutorial5.markdown +++ /dev/null @@ -1,209 +0,0 @@ ---- -title: TagBlog -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 list = createListingWith postManipulation - url "templates/postitem.html" - posts [("title", title)] - renderChain ["posts.html", "templates/default.html"] list -~~~~~ - -As you can see, `createListing` also has a `xxxWith` variant that lets you -specify a `ContextManipulation` to apply on every `Renderable`. 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 :: ContextManipulation -postManipulation = renderDate "date" "%B %e, %Y" "Unknown date" - . renderTagLinks tagToUrl -~~~~~ - -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 [FilePath] -readTagMap String [FilePath] -> Hakyll TagMap -~~~~~ - -You give it a list of paths, and it creates a map that, for every tag, holds -a number of posts. We can easily use this to render a post list for every tag. -The first argument given is an "identifier", unique to this tag map. Hakyll -needs this so it can cache the tags. - -~~~~~{.haskell} -tagMap <- readTagMap "postTags" 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} -renderTagCloud :: TagMap - -> (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 - index = createListingWith postManipulation "index.html" - "templates/postitem.html" - (take 3 renderablePosts) - [ ("title", "Home") - , ("tagcloud", 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/hakyll/tutorial6.markdown b/examples/hakyll/tutorial6.markdown deleted file mode 100644 index 5155d40..0000000 --- a/examples/hakyll/tutorial6.markdown +++ /dev/null @@ -1,88 +0,0 @@ ---- -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/tutorial6-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/tutorial6-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](examples/categoryblog.zip) containing the files used in this -tutorial. - -## 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") -~~~~~ - -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 string concatenation. The function that applies -this on every element in the `TagMap` is more interesting: - -~~~~~{.haskell} -categoryList :: TagMap -> String -categoryList = uncurry categoryListItem <=< toList -~~~~~ - -This function might seem a little harder to understand if you are not familiar -with the `<=<` operator. Figuring out how it works is left as an exercise for -the reader[^1]. - -[^1]: Hint: `>>=` is `concatMap` in the list monad. - -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). diff --git a/examples/hakyll/tutorial7.markdown b/examples/hakyll/tutorial7.markdown deleted file mode 100644 index cc6485a..0000000 --- a/examples/hakyll/tutorial7.markdown +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Tips and Tricks -what: some various tips and tricks about Hakyll ---- - -## Auto-compilation - -Hakyll features a simple _auto-compilation_ mode. This is invoked by running - -~~~~~ -[jasper@alice ~]$ ./hakyll preview -Starting hakyll server on port 8000... -~~~~~ - -Now, Hakyll will recompile your site when you change files, so you can just -refresh in your browser. There is one more thing to note: this will not update -your site automatically when `hakyll.hs` changes. So if you make any changes to -the configuration file, you'll have to compile it again, and then you can enter -`preview` mode again. - -## Pretty URL's - -There is an option in Hakyll to produce pretty URL's, which is disabled by -default because it can be confusing when you're first introduced to Hakyll. - -It can be enabled this way: - -~~~~~{.haskell} -import Text.Hakyll -import Text.Hakyll.Hakyll - -myHakyllConfiguration :: HakyllConfiguration -myHakyllConfiguration = defaultHakyllConfiguration - { enableIndexUrl = True - } - -main = hakyllWithConfiguration HakyllConfiguration $ do - -- Further code here -~~~~~ - -The effect will be that the internal `toUrl` function will behave differently. -A few examples: - -- `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`. diff --git a/examples/hakyll/tutorials/part07.markdown b/examples/hakyll/tutorials/part07.markdown index 3aeb953..98aeef2 100644 --- a/examples/hakyll/tutorials/part07.markdown +++ b/examples/hakyll/tutorials/part07.markdown @@ -1,6 +1,6 @@ --- title: Interlude -what: some various tips and tricks about Hakyll +what: gives some various tips and tricks about Hakyll --- ## Auto-compilation diff --git a/examples/hakyll/tutorials/part08.markdown b/examples/hakyll/tutorials/part08.markdown index b786534..d0e5180 100644 --- a/examples/hakyll/tutorials/part08.markdown +++ b/examples/hakyll/tutorials/part08.markdown @@ -95,3 +95,10 @@ an arrow of this function. The `arr` function solves this problem easily. 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. |