From fa88a0e9228a831f2808cda5c859e882b6579ccd Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Thu, 11 Mar 2010 18:03:48 +0100 Subject: Started writing Hakyll 2.x tutorials. Updated brochure example. --- examples/brochure/hakyll.hs | 7 +- examples/hakyll/hakyll.hs | 16 +- examples/hakyll/images/arrow-composition.png | Bin 0 -> 16066 bytes examples/hakyll/sidebar.markdown | 12 +- examples/hakyll/templates/default.html | 6 +- examples/hakyll/tutorials/part01.markdown | 228 +++++++++++++++++++++++++++ examples/hakyll/tutorials/part02.markdown | 83 ++++++++++ src/Text/Hakyll/Render.hs | 1 + 8 files changed, 333 insertions(+), 20 deletions(-) create mode 100644 examples/hakyll/images/arrow-composition.png create mode 100644 examples/hakyll/tutorials/part01.markdown create mode 100644 examples/hakyll/tutorials/part02.markdown diff --git a/examples/brochure/hakyll.hs b/examples/brochure/hakyll.hs index c550202..93a2393 100644 --- a/examples/brochure/hakyll.hs +++ b/examples/brochure/hakyll.hs @@ -1,11 +1,12 @@ import Text.Hakyll (hakyll) import Text.Hakyll.File (directory) import Text.Hakyll.Render (css, static, renderChain) -import Text.Hakyll.Renderables (createPagePath) +import Text.Hakyll.CreateContext (createPage) -main = hakyll $ do +main = hakyll "http://example.com" $ do directory css "css" render "about.rst" render "index.markdown" render "code.lhs" - where render = renderChain ["templates/default.html"] . createPagePath + where render = renderChain ["templates/default.html"] + . createPage diff --git a/examples/hakyll/hakyll.hs b/examples/hakyll/hakyll.hs index ca45175..53b9a4b 100644 --- a/examples/hakyll/hakyll.hs +++ b/examples/hakyll/hakyll.hs @@ -1,6 +1,6 @@ import Text.Hakyll import Text.Hakyll.Render -import Text.Hakyll.Renderables +import Text.Hakyll.CreateContext import Text.Hakyll.File import Text.Hakyll.Regex import Control.Monad.Reader (liftIO) @@ -8,17 +8,17 @@ import System.Directory import Control.Monad (mapM_, liftM) import Data.List (sort) -main = hakyll $ do +main = hakyll "http://jaspervdj.be/hakyll" $ do directory css "css" directory static "images" directory static "examples" directory static "reference" - tutorials <- liftIO $ liftM (sort . filter (`matchesRegex` "^tutorial[0-9]*.markdown$")) $ getDirectoryContents "." + tutorials <- liftM sort $ getRecursiveContents "tutorials" let tutorialPage = createListing "tutorials.html" - "templates/tutorialitem.html" - (map createPagePath tutorials) - [("title", "Tutorials")] + ["templates/tutorialitem.html"] + (map createPage tutorials) + [("title", Left "Tutorials")] renderChain ["templates/tutorials.html", "templates/default.html"] $ withSidebar tutorialPage mapM_ render' $ [ "about.markdown" @@ -29,6 +29,6 @@ main = hakyll $ do ] ++ tutorials where - render' = renderChain ["templates/default.html"] . withSidebar . createPagePath - withSidebar a = a `combine` createPagePath "sidebar.markdown" + render' = renderChain ["templates/default.html"] . withSidebar . createPage + withSidebar a = a `combine` createPage "sidebar.markdown" diff --git a/examples/hakyll/images/arrow-composition.png b/examples/hakyll/images/arrow-composition.png new file mode 100644 index 0000000..947561a Binary files /dev/null and b/examples/hakyll/images/arrow-composition.png differ diff --git a/examples/hakyll/sidebar.markdown b/examples/hakyll/sidebar.markdown index 3212d61..9444c58 100644 --- a/examples/hakyll/sidebar.markdown +++ b/examples/hakyll/sidebar.markdown @@ -1,9 +1,9 @@ --- sidebar ## Navigation -[home](index.html) -[philosophy](philosophy.html) -[about](about.html) -[tutorials](tutorials.html) -[reference](reference.html) -[changelog](changelog.html) +[home]($root/index.html) +[philosophy]($root/philosophy.html) +[about]($root/about.html) +[tutorials]($root/tutorials.html) +[reference]($root/reference.html) +[changelog]($root/changelog.html) diff --git a/examples/hakyll/templates/default.html b/examples/hakyll/templates/default.html index 9bcd33a..2e94f72 100644 --- a/examples/hakyll/templates/default.html +++ b/examples/hakyll/templates/default.html @@ -7,8 +7,8 @@ Hakyll - $title - - + + @@ -17,7 +17,7 @@
diff --git a/examples/hakyll/tutorials/part01.markdown b/examples/hakyll/tutorials/part01.markdown new file mode 100644 index 0000000..9647248 --- /dev/null +++ b/examples/hakyll/tutorials/part01.markdown @@ -0,0 +1,228 @@ +--- +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]: 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) +main = hakyll "http://example.com" $ do + liftIO $ 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. 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 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} + + + MyAweSomeCompany - $title + + + + +

MyAweSomeCompany - $title

+ + + $body + + +~~~~~ + +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.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. diff --git a/examples/hakyll/tutorials/part02.markdown b/examples/hakyll/tutorials/part02.markdown new file mode 100644 index 0000000..8fdbc78 --- /dev/null +++ b/examples/hakyll/tutorials/part02.markdown @@ -0,0 +1,83 @@ +--- +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 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. + +## The gist of it + +- Arrows really aren't complicated. +- Compose them using `>>>`. +- `HakyllAction` tracks dependencies for you. Use it. diff --git a/src/Text/Hakyll/Render.hs b/src/Text/Hakyll/Render.hs index 6f06953..d0bd138 100644 --- a/src/Text/Hakyll/Render.hs +++ b/src/Text/Hakyll/Render.hs @@ -6,6 +6,7 @@ module Text.Hakyll.Render , renderChain , static , css + , writePage ) where import Control.Arrow ((>>>)) -- cgit v1.2.3