diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/hakyll/examples/brochure.zip | bin | 3672 -> 3689 bytes | |||
-rw-r--r-- | examples/hakyll/images/arrow-composition.png | bin | 16066 -> 0 bytes | |||
-rw-r--r-- | examples/hakyll/images/tutorial8-categories.png | bin | 9377 -> 0 bytes | |||
-rw-r--r-- | examples/hakyll/images/tutorial8-tags.png | bin | 8631 -> 0 bytes | |||
-rw-r--r-- | examples/hakyll/tutorials/part01.markdown | 235 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part02.markdown | 91 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part03.markdown | 174 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part04.markdown | 60 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part05.markdown | 157 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part06.markdown | 84 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part07.markdown | 221 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part08.markdown | 97 | ||||
-rw-r--r-- | examples/hakyll/tutorials/part09.markdown | 104 |
13 files changed, 0 insertions, 1223 deletions
diff --git a/examples/hakyll/examples/brochure.zip b/examples/hakyll/examples/brochure.zip Binary files differindex bcdfc67..619d9a9 100644 --- a/examples/hakyll/examples/brochure.zip +++ b/examples/hakyll/examples/brochure.zip diff --git a/examples/hakyll/images/arrow-composition.png b/examples/hakyll/images/arrow-composition.png Binary files differdeleted file mode 100644 index 947561a..0000000 --- a/examples/hakyll/images/arrow-composition.png +++ /dev/null diff --git a/examples/hakyll/images/tutorial8-categories.png b/examples/hakyll/images/tutorial8-categories.png Binary files differdeleted file mode 100644 index 0567917..0000000 --- a/examples/hakyll/images/tutorial8-categories.png +++ /dev/null diff --git a/examples/hakyll/images/tutorial8-tags.png b/examples/hakyll/images/tutorial8-tags.png Binary files differdeleted file mode 100644 index d8ae73e..0000000 --- a/examples/hakyll/images/tutorial8-tags.png +++ /dev/null diff --git a/examples/hakyll/tutorials/part01.markdown b/examples/hakyll/tutorials/part01.markdown deleted file mode 100644 index f96993a..0000000 --- a/examples/hakyll/tutorials/part01.markdown +++ /dev/null @@ -1,235 +0,0 @@ ---- -title: Quickstart -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] using [cabal-install]. This tutorial also assumes you have a -basic knowledge of Haskell. - -[hackage]: http://hackage.haskell.org/ -[cabal-install]: http://www.haskell.org/haskellwiki/Cabal-Install - -~~~~~ -[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] 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). - -[zip file]: $root/examples/brochure.zip - -~~~~~ -[jasper@alice brochure]$ ghc --make hakyll.hs -[1 of 1] Compiling Main ( hakyll.hs, hakyll.o ) -Linking hakyll ... -[jasper@alice brochure]$ ./hakyll preview -Starting hakyll server on port 8000... -~~~~~ - -If you now point your browser at [localhost:8000] you should see our simple -brochure site. - -[localhost:8000]: http://localhost:8000/ - -## 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) -import Control.Monad.Trans (liftIO) -main = hakyll "http://example.com" $ do - liftIO $ putStrLn "I'm in your computer, generating your site!" -~~~~~ - -Note how we wrap everything in the `hakyll` function. This is useful because -it will generate a very nice main function. We also pass the full site URL to -the `hakyll` function. If you don't have an URL for your site yet, it doesn't -really matter for now; just fill in anything then. The URL is only used for -certain specific purposes where a full URL is needed, such as rendering RSS -feeds. - -## Context - -Let's look at one of the most important types in Hakyll. - -~~~~~{.haskell} -type Context = Map String String -~~~~~ - -A `Context` is a key-value mapping, used to represent pieces of information. -One way to write such a `Context`, is a page. - -## Pages - -Another 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 surrounded 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 are a few "special" keys in Hakyll: one of them is 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 `Context`. In our case, we only have one -template, and our `Context` is the about page we just saw - we can load that -using the `createPage` function. - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -import Text.Hakyll.Render (renderChain) -import Text.Hakyll.CreateContext (createPage) -main = hakyll "http://example.com" $ do - renderChain ["templates/default.html"] - (createPage "index.markdown") -~~~~~ - -Or, to render all our three pages: - -~~~~~{.haskell} -import Text.Hakyll (hakyll) -import Text.Hakyll.Render (renderChain) -import Text.Hakyll.CreateContext (createPage) -main = hakyll "http://example.com" $ do - render "about.rst" - render "index.markdown" - render "code.lhs" - where render = renderChain ["templates/default.html"] - . createPage -~~~~~ - -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 "http://example.com" $ 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 "http://example.com" $ 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 "http://example.com" $ 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. - -## The gist of it - -- You render "pages" with "templates". -- The most common render function is `renderChain`. -- Hakyll also deals with static files and css. diff --git a/examples/hakyll/tutorials/part02.markdown b/examples/hakyll/tutorials/part02.markdown deleted file mode 100644 index 3df3c1f..0000000 --- a/examples/hakyll/tutorials/part02.markdown +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: Arrows: A Crash Course -what: illustrates how Arrows are used in hakyll ---- - -## Do I really need to know this stuff? - -Maybe. You don't need it when you only use the basic Hakyll functions, but -Arrows are used a lot in the Hakyll code, and a lot of the more advanced -features make use of Arrows. Besides, it's a pretty interesting subject. - -## What is an "Arrow" - -Arrows are comparable with monads. In fact, monads are a subset of arrows. -Arrows allow you to represent a "computation". This is all pretty vague, so -let's skip directly to the Arrows used in Hakyll. - -## HakyllAction - -The Arrow used throughout Hakyll is called `HakyllAction`. Arrows have two -type parameters, so it's actually `HakyllAction a b`. You can think of `a` -as the input for our action, and `b` is the corresponding output. Let's look -at the type of `createPage`: - -~~~~~{.haskell} -createPage :: FilePath -> HakyllAction () Context -~~~~~ - -So, you give `createPage` a `FilePath`, and it creates a `HakyllAction` that -produces a `Context` out of thin air. Now, we want to render the `Context` we -just loaded with a template. The type of the `render` function is: - -~~~~~{.haskell} -render :: FilePath -> HakyllAction Context Context -~~~~~ - -We pass the file name of a template to the `render` function, and we get a -`HakyllAction` that creates a `Context` from another `Context`. The result -of the `render` operation (so basically the rendered template) will be placed -in the `$body` field of the new `Context`. But we still haven't saved our -result, so let's do that using the `writePage` function. - -~~~~~{.haskell} -writePage :: HakyllAction Context () -~~~~~ - -This function writes our result and returns nothing. - -## Composition - -Now, let's look at the big picture. - -![Arrow illustration]($root/images/arrow-composition.png) - -If these were regular functions, we could've composed them using the `.` -operator. Since they're arrows, we'll have to use the `>>>` operator. - -~~~~~{.haskell} -test :: HakyllAction () () -test = createPage "test.markdown" - >>> render "template.html" - >>> writePage -~~~~~ - -Now, we only have to execute our test. - -~~~~~{.haskell} -runHakyllActionIfNeeded test -~~~~~ - -## Aso, the point emerges - -The `runHakyllActionIfNeeded` suggests why we use arrows. `HakyllAction` is more -than just a function, it also tracks dependencies. This causes Hakyll to only -execute our functions when it is really needed. In this particular case, `test` -would only be executed if either `test.markdown` or `template.html` were -recently changed. - -## So what's renderChain then? - -Now, we have done pretty much the same as we did with the `renderChain` function -in the first part. That's right, the `renderChain` is more or less implemented -like this. So, you will probably use `renderChain` in most cases, but it's -handy if you know how it works. - -## The gist of it - -- Arrows really aren't complicated. -- Compose them using `>>>`. -- `HakyllAction` tracks dependencies for you. Use it. -- In most cases, you will just use `renderChain`. diff --git a/examples/hakyll/tutorials/part03.markdown b/examples/hakyll/tutorials/part03.markdown deleted file mode 100644 index 0ba633e..0000000 --- a/examples/hakyll/tutorials/part03.markdown +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: How to write pages -what: elaborates a little on writing pages ---- - -## The structure of a Page - -The most important thing to realize is that a page is reduced to a `Context`, -and therefore 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 Hakyll 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. - - --- - The body comes last, and is optional. - -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. -- `$body`: The body comes last, and is optional. - -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 Contexts - -Now you know that pages, and `Context`s in general, are basically nothing more -than key-values mappings, it is time to abuse this fact. There is another -way to create a `Context`, called `combine`. - -The type signature of the `combine` function does a pretty good job at -explaining it: - -~~~~~{.haskell} -combine :: HakyllAction () Context - -> HakyllAction () Context - -> HakyllAction () Context -~~~~~ - -This means we can take two `Context`s values and combine them. This is -basically a `Map.union`: The result will contain all keys from both `Context`s, -with there corresponding values. If a key is present in both `Context`s, the -value from the first argument will be chosen. This is, for example, almost -always the case with the `$url` field (since almost all `Context`s have an url -in Hakyll). - -Combining two `Context`s, but overriding the `$url` is quite common, so there is -another function that helps us here: - -~~~~~{.haskell} -combineWithUrl :: FilePath - -> HakyllAction () Context - -> HakyllAction () Context - -> HakyllAction () Context -~~~~~ - -## 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] containing the -source code for the tutorial. - -[is a zip file]: $root/examples/morepages.zip - -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 - so we need to -add the footer page to every `Context`. We write a small auxiliary function -that combines a given `Context` with the footer: - -~~~~~{.haskell} -withFooter = flip combine $ createPage "footer.markdown" -~~~~~ - -Note that we use `flip` here - we want `footer.markdown` to be our second -argument. That is because Hakyll will take the `$url` from the first `Context`, -so all pages would be rendered to `footer.html` - obviously not what we want. -Now, were we previously wrote: - -~~~~~{.haskell} -render "about.markdown" -where render = renderChain ["templates/default.html"] - . createPage -~~~~~ - -We simply have to add our footer: - -~~~~~{.haskell} -render "about.markdown" -where render = renderChain ["templates/default.html"] - . withFooter - . createPage -~~~~~ - -And now every page will include the footer. - -## The gist of it - -- Pages are just key-value mappings. -- You can have multiple sections in every page. -- Combine pages using the `combine` function. diff --git a/examples/hakyll/tutorials/part04.markdown b/examples/hakyll/tutorials/part04.markdown deleted file mode 100644 index 22fc846..0000000 --- a/examples/hakyll/tutorials/part04.markdown +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: How to write templates -what: more information on template writing ---- - -## Simple templates - -Simple templates are simply HTML files, with `$identifiers`. An example: - -~~~~~{.html} -<html> - <head> - <title>$title</title> - </head> - <body> - $body - </body> -</html> -~~~~~ - -## Markup in templates - -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: - - # $title - - _On $date_ - - $body - -__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. - -## Hamlet templates - -From Hakyll 2.3 onwards, it is possible to use [hamlet] templates. You can find -more information about hamlet on that website. Usage is fairly simple -- since -pages are strictly key-value mappings, only `$variable$` control is supported in -hamlet templates. As an example, here is the template that can be used for the -brochure site, but in hamlet: - - !!! - %html - %head - %title MyAweSomeCompany - $$title$ - %body - %h1 MyAweSomeCompany - $$title$ - #navigation - %a!href="$$root$/index.html" Home - %a!href="$$root$/about.html" About - %a!href="$$root$/code.html" Code - $body$ - -Hakyll will recognise hamlet templates automatically by the `.hamlet` extension. - -[hamlet]: http://docs.yesodweb.com/hamlet/ diff --git a/examples/hakyll/tutorials/part05.markdown b/examples/hakyll/tutorials/part05.markdown deleted file mode 100644 index be71714..0000000 --- a/examples/hakyll/tutorials/part05.markdown +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: Creating a Blog -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 for this tutorial is also available. - -[zip file]: $root/examples/simpleblog.zip - -Blogs, as you probably know, are composed of posts. In Hakyll, we're going -to use simple pages for posts. All posts are located in the `posts` -directory. But we're not going to use the `directory` command here - you will -see why later. First, some trivial things like css. - -~~~~~{.haskell} -main = hakyll "http://example.com" $ do - directory css "css" -~~~~~ - -## Finding the posts - -`Text.Hakyll.File` contains a handy function `getRecursiveContents`, which will -provide us with all the blog posts. The blog posts have a -`yyyy-mm-dd-title.extension` naming scheme. This is just a simple trick so we -can sort them easily (sorting on filename implies sorting on date). You could of -course name them whatever you want, but it's always a good idea to stick to the -conventions. They contain some metadata, too: - - title: A first post - author: Julius Caesar - date: November 5, 2009 - --- - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Vivamus pretium leo adipiscing lectus iaculis lobortis. - Vivamus scelerisque velit dignissim metus... - -Now, we find the posts and sort them reversed, so the most recent post will -become the first item in the list: - -~~~~~{.haskell} -postPaths <- liftM (reverse . sort) $ getRecursiveContents "posts" -~~~~~ - -Our `postPaths` value is now of the type `[FilePath]`. We want to be able to -render all posts, so we pass them to the `createPage` function. - -~~~~~{.haskell} -let postPages = map createPage postPaths -~~~~~ - -We have two templates we want to render our posts with: first we would like to -render them using `templates/post.html`, and we want to render the result -using `templates/default.html`. This can be done with the `renderChain` -function: - -~~~~~{.haskell} -mapM_ (renderChain [ "templates/post.html" - , "templates/default.html" - ]) postPages -~~~~~ - -Remember that the `renderChain` works by rendering the item using the first -template, creating a new page with the render result in the `$body` field, and -so on until it has been rendered with all templates. - -Now, we have the posts rendered. What is left is to generate some kind of index -page with links to those posts. We want one general list showing all posts, and -we want to show a few recent posts on the index page. - -## Creating listings. - -`createPage` is the easiest way of reading a `Context`. But in this case, we -want something more custom, so we'll use the `createCustomPage` function. This -allows us to create a more specific `Context`. - -~~~~~{.haskell} -createCustomPage :: FilePath - -> [(String, Either String (HakyllAction () String))] - -> HakyllAction () Context -~~~~~ - -The first argument is the `url` of the page to generate. For our index page, -this will be, `index.html`. The second argument is obviously our `key: value` -mapping. But why the `Either`? This, once again, is about dependency handling. -The idea is that you can choose which type to use for the value: - -- `String`: Simply a `String`. -- `HakyllAction () String`: Here, you can give an `HakyllAction` Arrow action - that can produce a String. However - this action _will not be executed_ when - the file in `_site` is up-to-date. - -However, in this specific case - a list of posts - there is an easier, and more -high-level approach than `createCustomPage`[^1]. Let's look at the type -signature of `createListing`: - -~~~~~{.haskell} -createListing :: FilePath - -> [FilePath] - -> [HakyllAction () Context] - -> [(String, Either String (HakyllAction () String))] - -> HakyllAction () Context -~~~~~ - -[^1]: Since Hakyll-1.3 onwards. - -The first argument is the destination url. For our blog, this is of course -`index.html`. The second argument is a list templates to render _each_ `Context` -with. We use only `templates/postitem.html` here. This is, as you can see, a -simple template: - -~~~~~{.html} -<li> - <a href="$$root/$url">$title</a> - - <em>$date</em> - by <em>$author</em> -</li> -~~~~~ - -We then give a list of `Context`s to render. For our index, these are the 3 last -posts. The last argument of the `createListing` functions lets you specify -additional key-value pairs, like in `createCustomPage`. We use this to set the -title of our page. So, we create our index page using: - -~~~~~{.haskell} -let index = createListing "index.html" - ["templates/postitem.html"] - (take 3 postPages) - [("title", Left "Home")] -~~~~~ - -The result of this will be a `HakyllAction () Context`. This `Context`'s `$body` -will contain a concatenation of all the 3 posts, rendered with the -`templates/postitem.html` template. - -Now, we only have to render it: first using the `index.html` template - which -adds some more information to our index - then using the -`templates/default.html` template. - -~~~~~{.haskell} -renderChain ["index.html", "templates/default.html"] index -~~~~~ - -Note that the `index.html` in the `renderChain` list is also a template. Now, -you might want to take your time to read the `index.html` template and the other -files in the zip so you understand what is going on here. - -## The gist of it - -- 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 deleted file mode 100644 index d64dc55..0000000 --- a/examples/hakyll/tutorials/part06.markdown +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Creating feeds -what: adds an rss feed to the simple blog ---- - -## Adding Feeds - -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. - -[zip file]: $root/examples/feedblog.zip - -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. - -[^1]: Since Hakyll-2.0 - -This is the first time that the absolute URL of your site you have to give to -the `hakyll` function actually matters. That's because the specifications of -those feed formats dictate you need the full URL of them. - -## Creating a configuration - -The first thing to do is creating a configuration for your feed. You could -place this code outside of your main function, as is done in the example. - -~~~~~{.haskell} -myFeedConfiguration = FeedConfiguration - { feedUrl = "rss.xml" - , feedTitle = "SimpleBlog RSS feed." - , feedDescription = "Simple demo of a feed created with Hakyll." - , feedAuthorName = "Jasper Van der Jeugt" - } -~~~~~ - -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} -renderRss myFeedConfiguration (take 3 postPages) -~~~~~ - -## But it's not that easy - -If you look at our generated RSS feed (build the site), you will see -`$description` tags appearing in our final render. We don't want that! How -did they get there in the first place? - -To render feeds, Hakyll expects a number of fields in the renderables you put -in the feed. They are: - -- `$title`: Title of the item. This is set in our posts, since we use a `title` - metadata field. -- `$url`: Url of the item. This is automatically set by Hakyll, so you shouldn't - worry about it. -- `$description`: A description of our item to appear in the feed reader. - -The latter is obviously the problem: we don't have a description in our posts. -In fact, we would like to copy the `$body` key to the `$description` key, so -people can read the full post in their feed readers. - -## Where arrows come in - -The `Text.Hakyll.ContextManipulations` module contains a number of simple -functions that create Arrows for us. One of these functions is `copyValue`, -which takes a source and a destination key. So, we need to pass our -items through this Arrow first. - -~~~~~{.haskell} -renderRss myFeedConfiguration $ - map (>>> copyValue "body" "description") (take 3 postPages) -~~~~~ - -And that's that, now our feed gets rendered properly. Exercise for the reader -is to add a Atom feed[^2]. - -[^2]: Hint: look around in the [reference]($root/reference.html). - -## 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`. diff --git a/examples/hakyll/tutorials/part07.markdown b/examples/hakyll/tutorials/part07.markdown deleted file mode 100644 index d017a1e..0000000 --- a/examples/hakyll/tutorials/part07.markdown +++ /dev/null @@ -1,221 +0,0 @@ ---- -title: Tags and manipulations -what: enhances our blog with tags and explains context manipulations. ---- - -## Context manipulations - -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 -~~~~~ - -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 -~~~~~ - -For further reading, refer to the `Text.Hakyll.ContextManipulations` -documentation. - -## Applying Context Manipulations - -Because we're dealing with Arrows again, we can use `>>>` to apply our -manipulations. For example, we could use or title manipulation like this: - -~~~~~{.haskell} -renderChain ["templates/default.html"] - (createPage "index.markdown" >>> titleUpper) -~~~~~ - -## Rendering dates - -As you remember, in our previous blog, all posts had a file name like -`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also -had a metadata field `date`, containing a human-readable date. This is not very -D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with -dates encoded in paths: `renderDate`. - -~~~~~{.haskell} -postManipulation :: HakyllAction Context Context -postManipulation = renderDate "date" "%B %e, %Y" "Unknown date" -~~~~~ - -That manipulation will: -- Read the date from the file name the post was loaded from. -- Parse the date and render it in a `%B %e, %Y` format. This is a - `Month day, Year` format. -- Put the result in the `date` metadata field. -- If the date could not be parsed, it will put `"Unknown date"` in the `date` - metadata field. - -So, we can throw away our `date: ` lines from our posts, and still use `$date` -in our templates. - -## Abstracting the post list - -Now, we're going to render tags. This is also done using context manipulations. -Hakyll has a specialized module to deal with tags, provided by -`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in -the `tags` metadata field. - - --- - title: A third post - author: Publius Ovidius Naso - tags: epic fail, ovidius - --- - Pellentesque tempor blandit elit, vel... - -But first things first. We need to render a post list for every tag. We already -had some code to render a list of all posts. We're just going to abstract this -code into a more general function: - -~~~~{.haskell} -renderPostList url title posts = do - let list = createListingWith url ["templates/postitem.html"] - posts [("title", Left title)] - renderChain ["posts.html", "templates/default.html"] list -~~~~~ - -Our "render all posts" action can now be written as: - -~~~~~{.haskell} -renderPostList "posts.html" "All posts" renderablePosts -~~~~~ - -## Tag links - -We want to display the tags for our post under the title. But if we use the -`$tags` key in a template, we will just have the plain tags - they will not be -clickable. We can again solve this with a `ContextManipulation`. We have a -function that produces an url for a given tag: - -~~~~~{.haskell} -tagToUrl tag = "$root/tags/" ++ removeSpaces tag ++ ".html" -~~~~~ - -`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is -a specialized `renderValue` function for creating linked tags called -`renderTagLinks`. This function simply takes a function that produces an url -for a given tag - the function we just wrote. Let's extend our -`postManipulation`. - -~~~~~{.haskell} -postManipulation :: HakyllAction Context Context -postManipulation = renderDate "date" "%B %e, %Y" "Unknown date" - >>> renderTagLinks tagToUrl -~~~~~ - -We apply this manipulation when we load the tags. - -~~~~~{.haskell} -let renderablePosts = - map ((>>> postManipulation) . createPage) postPaths -~~~~~ - -So, the `renderTagLinks` function replaces the `$tags` value from -`epic fail, random` to `<a href="$root/tags/epic-fail.html">epic fail</a>, ...`. -If we click a tag, we get a `404`. That's because we haven't generated the -post lists for every tag. - -## The Tag Map - -Hakyll provides a function called `readTagMap`. Let's inspect it's type. - -~~~~~{.haskell} -type TagMap = Map String [HakyllAction () Context] -readTagMap String [FilePath] -> HakyllAction () TagMap -~~~~~ - -You give it a list of paths, and it creates a map that, for every tag, holds -a number of posts. We can easily use this to render a post list for every tag. -The first argument given is an "identifier", unique to this tag map. Hakyll -needs this so it can cache the tags. - -~~~~~{.haskell} -let tagMap = readTagMap "postTags" postPaths -~~~~~ - -When we have the `TagMap`, we can need to render a post list for every tag. -There is a function in Hakyll designed more or less for this purpose: -`withTagMap`. This takes a `TagMap` and an action to execute for every tag and -it's associated posts. We pass a small function to it we create ourselves[^1]: - -[^1]: Exercise for the reader: why do we use `>>> postManipulation` again here? - -~~~~~{.haskell} -let renderListForTag tag posts = - renderPostList (tagToUrl tag) - ("Posts tagged " ++ tag) - (map (>>> postManipulation) posts) -withTagMap tagMap renderPostList -~~~~~ - -There we go. We now have clickable tags, and a post list for every tag. - -## A Tag Cloud - -A tag cloud is a commonly found thing on blogs. Hakyll also provides code to -generate a tag cloud. Let's have a look at the `renderTagCloud` function. - -~~~~~{.haskell} -renderTagCloud :: (String -> String) - -> Float - -> Float - -> HakyllAction TagMap String -~~~~~ - -The first argument is, once again, a function to create an url for a given tag. -Then, we give a minimum and a maximum font size in percent, and we get a tag -cloud Arrow back. We can add this to our index: - -~~~~~{.haskell} -let tagCloud = tagMap >>> renderTagCloud tagToUrl 100 200 - index = createListing "index.html" - ["templates/postitem.html"] - (take 3 renderablePosts) - [ ("title", Left "Home") - , ("tagcloud", Right tagCloud) - ] -renderChain ["index.html", "templates/default.html"] index -~~~~~ - -## The gist of it - -- There's some handy, simple functions in `Text.Hakyll.ContextManipulations`. -- Seperate tags by commas and put them in the `$tags` field. -- Use `withTagMap` to render a list for every tag. -- Hakyll can also create tag clouds. diff --git a/examples/hakyll/tutorials/part08.markdown b/examples/hakyll/tutorials/part08.markdown deleted file mode 100644 index ec27153..0000000 --- a/examples/hakyll/tutorials/part08.markdown +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: Interlude -what: gives some various tips and tricks about Hakyll (quite handy, read this!) ---- - -## Syntax-highlighting - -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: - -~~~~~ -[jasper@alice ~]$ cabal install --reinstall -fhighlighting pandoc -~~~~~ - -## 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 refresh in your browser. 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. - -If you use a custom `HakyllConfiguration`, you can select your custom -`PreviewMode`: - -- `BuildOnRequest`: rebuild site when the preview server receives a request - (default). -- `BuildOnInterval`: build when you change files. - -## When to rebuild - -If you execute a `./hakyll build`, Hakyll will build your site incrementally. -This means it will be very fast, but it will not pick up _all_ changes. - -- In case you edited `hakyll.hs`, you first want to compile it again. -- It is generally recommended to do a `./hakyll rebuild` before you deploy your - site. - -After rebuilding your site, all files will look as "modified" to the filesystem. -This means that when you upload your site, it will usually transfer all files -- -this can generate more traffic than necessary, since it is possible that some -files were not actually modified. If you use `rsync`, you can counter this using -the `--checksum` option. - -## 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.HakyllMonad - -myConfig :: HakyllConfiguration -myConfig = (defaultHakyllConfiguration "http://jaspervdj.be") - { enableIndexUrl = True - } - -main = hakyllWithConfiguration myConfig $ 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`. - -## Default values - -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. - -- 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. - -- Another option is to use a `defaults.markdown` file, simply containing some - metadata, and then `combine` this file with other pages. The advantage is - that autocompilation mode will pick up changes in this file[^1]. - -[^1]: Original idea by zenzike. diff --git a/examples/hakyll/tutorials/part09.markdown b/examples/hakyll/tutorials/part09.markdown deleted file mode 100644 index 4cc1d43..0000000 --- a/examples/hakyll/tutorials/part09.markdown +++ /dev/null @@ -1,104 +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/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. |