From 8a8dade15ffc84f543fc3774f7a0a8fac4835bd2 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Sun, 16 Dec 2012 00:21:59 +0100 Subject: Better tutorial structure --- web/tutorials/02-basics.markdown | 121 ++---------- web/tutorials/03-compilers.markdown | 33 ---- web/tutorials/03-rules-routes-compilers.markdown | 117 ++++++++++++ web/tutorials/04-arrows.markdown | 223 ----------------------- web/tutorials/04-compilers.markdown | 38 ++++ web/tutorials/05-arrows.markdown | 223 +++++++++++++++++++++++ web/tutorials/05-guide.markdown | 129 ------------- web/tutorials/06-guide.markdown | 129 +++++++++++++ 8 files changed, 523 insertions(+), 490 deletions(-) delete mode 100644 web/tutorials/03-compilers.markdown create mode 100644 web/tutorials/03-rules-routes-compilers.markdown delete mode 100644 web/tutorials/04-arrows.markdown create mode 100644 web/tutorials/04-compilers.markdown create mode 100644 web/tutorials/05-arrows.markdown delete mode 100644 web/tutorials/05-guide.markdown create mode 100644 web/tutorials/06-guide.markdown (limited to 'web/tutorials') diff --git a/web/tutorials/02-basics.markdown b/web/tutorials/02-basics.markdown index 46d85a6..77bbefc 100644 --- a/web/tutorials/02-basics.markdown +++ b/web/tutorials/02-basics.markdown @@ -19,115 +19,26 @@ directories: In general, it's only necessary to use `rebuild` when you made changes to your `site.hs`, and not when you just made changes to the contents of your website. -Basic rules ------------ +At this point, feel free to change some files, `./site build` and see what +happens! -Let's take a look at the `site.hs` file. +Pages and metadata +------------------ -```haskell -main :: IO () -main = hakyll $ do - ... -``` +You might've noticed that the markdown pages all start with a block: -Hakyll configurations are in the `Rules` monad. In order to run them, the -`hakyll` function is used, so your main function usually starts this way. -`hakyllWith` is also available, this function allows you specify a custom -[Configuration]. + --- + title: Contact + --- -[Configuration]: /reference/Hakyll-Core-Configuration.html + I live... -Some actual rules look like this: +This is entirely optional, but useful for providing extra information +("metadata") about items. All items can have metadata: since it's not really +convenient to add such a header to an image, you can also do this using a +separate file. -```haskell -match "images/*" $ do - route idRoute - compile copyFileCompiler +For a file called `images/foo.png`, you can add an `images/foo.png.metadata` +file with contents: -match "css/*" $ do - route idRoute - compile compressCssCompiler - -``` - -This is a declarative DSL: the order in which you write the rules make little -difference: Hakyll will use dependency tracking to determine the correct order. - -We group the different rules using `match`. The first argument for `match` is a -[Pattern]. The `OverloadedStrings` extension allows us to just write `String`s -here, which are interpreted as globs --- all files in the `images/` directory, -and all files in the `css/` directory. - -[Pattern]: /reference/Hakyll-Core-Identifier-Pattern.html - -Basic routes ------------- - -The `route` function is used for determining the output file. For example, you -probably want to write the processed contents of `contact.markdown` to -`_site/contact.html` and not `_site/contact.markdown`. - -`idRoute` is a commonly used and just keeps the filename. We use this for e.g. -the images and CSS files. - -`setExtension` is another common route which takes a single argument: the -extension of the resulting file. In order to route `contact.markdown` to -`_site/contact.html`, use: - -```haskell -route $ setExtension "html" -``` - -`customRoute` is a more advanced higher-order function which allows for even -more customization. You want to route `contact.markdown` to -`_site/nwodkram.tcatnoc`? No problem, just use: - -```haskell -customRoute $ reverse . toFilePath -``` - -Basic compilers ---------------- - -More information can be found in the [Routes] module. - -[Routes]: /reference/Hakyll-Core-Routes.html - -The `compile` function determines how the content is produced for a certain -item. `compile` takes a value of the type `Compiler (Item a)`. Let's look at -some common examples: - -- `copyFileCompiler` is self-explanatory, the output is exactly the same as the - input; -- `getResourceBody` just gets you the content as a `String`, so you can perform - other manipulations afterwards; -- `compressCssCompiler` performs some simple build-in compression - transformations for CSS; -- `pandocCompiler` reads markdown, reStructuredText, or another input format and - renders it as HTML (if you want to pass specific options to pandoc, use - `pandocCompilerWith`). - -Compilers are very flexible: `Compiler` is a [Monad] and `Item` is a [Functor]. - -[Monad]: http://learnyouahaskell.com/a-fistful-of-monads -[Functor]: http://learnyouahaskell.com/functors-applicative-functors-and-monoids - -A good example to illustrate the `Monad` instance for `Compiler` is - -```haskell -relativizeUrls :: Item String -> Compiler (Item String) -``` - -This compiler traverses your HTML and changes absolute URLs (e.g. -`/posts/foo.markdown` into relative ones: `../posts/foo.markdown`). This is -extremely useful if you want to deploy your site in a subdirectory (e.g. -`jaspervdj.be/hakyll` instead of `jaspervdj.be`). Combining this with the -`pandocCompiler` gives us: - -```haskell -pandocCompiler >>= relativizeUrls :: Compiler (Item String) -``` - -For a real website, you probably also want to use templates in order to give -your pages produced by pandoc a nice layout. We tackle this in the next -tutorial. + title: An image of a cow diff --git a/web/tutorials/03-compilers.markdown b/web/tutorials/03-compilers.markdown deleted file mode 100644 index 953464f..0000000 --- a/web/tutorials/03-compilers.markdown +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: More on compilers: load, and templates -author: Jasper Van der Jeugt ---- - -Loading items -------------- - -The compiler Monad is a complex beast, but this is nicely hidden for the user of -the Hakyll library. - -Suppose that you're generating `index.html` which shows your latest brilliant -blogpost. This requires `posts/foo.markdown` to be generated *before* -`index.html` (so we don't have to generate it twice). But you don't have to care -about all of that: Hakyll will sort this out this out for you automatically! - -Let's see some quick examples. We can load a specific item: - -```haskell -load "posts/foo.markdown" :: Compiler (Item String) -``` - -Or a whole bunch of them: - -```haskell -loadAll "posts/*" :: Compiler [Item String] -``` - -Sometimes you just want the *contents* and not the `Item`: - -```haskell -loadBody "posts/foo.markdown" :: Compiler String -``` diff --git a/web/tutorials/03-rules-routes-compilers.markdown b/web/tutorials/03-rules-routes-compilers.markdown new file mode 100644 index 0000000..b3bf4ee --- /dev/null +++ b/web/tutorials/03-rules-routes-compilers.markdown @@ -0,0 +1,117 @@ +--- +title: Rules, routes and compilers +author: Jasper Van der Jeugt +--- + +Basic rules +----------- + +While changing the content is nice, you'll have time for that once your site is +configured. Configuration is done using the `site.hs` file: let's take a look at +it! + +```haskell +main :: IO () +main = hakyll $ do + ... +``` + +Hakyll configurations are in the `Rules` monad. In order to run them, the +`hakyll` function is used, so your main function usually starts this way. +`hakyllWith` is also available, this function allows you specify a custom +[Configuration]. + +[Configuration]: /reference/Hakyll-Core-Configuration.html + +Some actual rules look like this: + +```haskell +match "images/*" $ do + route idRoute + compile copyFileCompiler + +match "css/*" $ do + route idRoute + compile compressCssCompiler + +``` + +This is a declarative DSL: the order in which you write the rules make little +difference: Hakyll will use dependency tracking to determine the correct order. + +We group the different rules using `match`. The first argument for `match` is a +[Pattern]. The `OverloadedStrings` extension allows us to just write `String`s +here, which are interpreted as globs --- all files in the `images/` directory, +and all files in the `css/` directory. + +[Pattern]: /reference/Hakyll-Core-Identifier-Pattern.html + +Basic routes +------------ + +The `route` function is used for determining the output file. For example, you +probably want to write the processed contents of `contact.markdown` to +`_site/contact.html` and not `_site/contact.markdown`. + +`idRoute` is a commonly used and just keeps the filename. We use this for e.g. +the images and CSS files. + +`setExtension` is another common route which takes a single argument: the +extension of the resulting file. In order to route `contact.markdown` to +`_site/contact.html`, use: + +```haskell +route $ setExtension "html" +``` + +`customRoute` is a more advanced higher-order function which allows for even +more customization. You want to route `contact.markdown` to +`_site/nwodkram.tcatnoc`? No problem, just use: + +```haskell +route $ customRoute $ reverse . toFilePath +``` + +More information can be found in the [Routes] module. + +[Routes]: /reference/Hakyll-Core-Routes.html + +Basic compilers +--------------- + +The `compile` function determines how the content is produced for a certain +item. `compile` takes a value of the type `Compiler (Item a)`. Let's look at +some common examples: + +- `copyFileCompiler` is self-explanatory, the output is exactly the same as the + input; +- `compressCssCompiler` performs some simple build-in compression + transformations for CSS; +- `pandocCompiler` reads markdown, reStructuredText, or another input format and + renders it as HTML (if you want to pass specific options to pandoc, use + `pandocCompilerWith`). + +Compilers are very flexible: `Compiler` is a [Monad] and `Item` is a [Functor]. + +[Monad]: http://learnyouahaskell.com/a-fistful-of-monads +[Functor]: http://learnyouahaskell.com/functors-applicative-functors-and-monoids + +A good example to illustrate the `Monad` instance for `Compiler` is + +```haskell +relativizeUrls :: Item String -> Compiler (Item String) +``` + +This compiler traverses your HTML and changes absolute URLs (e.g. +`/posts/foo.markdown` into relative ones: `../posts/foo.markdown`). This is +extremely useful if you want to deploy your site in a subdirectory (e.g. +`jaspervdj.be/hakyll` instead of `jaspervdj.be`). Combining this with the +`pandocCompiler` gives us: + +```haskell +pandocCompiler >>= relativizeUrls :: Compiler (Item String) +``` + +For a real website, you probably also want to use templates in order to give +your pages produced by pandoc a nice layout. We tackle this in the next +tutorial. diff --git a/web/tutorials/04-arrows.markdown b/web/tutorials/04-arrows.markdown deleted file mode 100644 index 3f2e244..0000000 --- a/web/tutorials/04-arrows.markdown +++ /dev/null @@ -1,223 +0,0 @@ ---- -title: Arrow Magic: Metadata Dependent Page Generation -author: Florian Hars ---- - -## Supporting a "published: false" attribute on pages - -Many content management systems or blog platforms support -some kind of workflow that display articles differently or -not at all depending on which state the article is in, for -example whether it has a "published" attribute or not. -Hakyll has no built-in support for anything like this, but since -its compilers are just arrows, it is easy to implement arbitrary -metadata dependent behaviour for rendering pages. - -Let's start by adding support for a "published" attributes to the -`simpleblog` example. We want to consider a blog post published if it -has a `published` metadata element that does not have the value -`false`. A function to test for this is simple: - -~~~~~{.haskell} -isPublished :: Page a -> Bool -isPublished p = - let published = getField "published" p in - published /= "" && published /= "false" -~~~~~ - -The next step is to write a function that tags a page with its -published status, which can be either unpublished or published, using -the standard `Either` datatype and then transform this function -into a `Compiler`. The latter can be done with the standard `arr` -function from `Control.Arrow`, which lifts a function into an arrow: - -~~~~~{.haskell} -isPagePublished :: Compiler (Page a) (Either (Page a) (Page a)) -isPagePublished = arr (\p -> if isPublished p then Right p else Left p) -~~~~~ - -For the next processing steps we now need a compiler that takes an -`Either (Page a) (Page a)` instead of the usual `Page a` as an -input. But the former can be built up from the latter using some -standard combinators from the `Control.Arrow` library. The simplest -one is `|||`, which takes two compilers (arrows) with the same output -type and returns a new compiler that takes an `Either` of the input -types of the Compilers as an input. Maybe we just want to render our -unpublished posts with a big warning that they are provisional, so we -just want to render the unpublished `Left` pages with another template -than the published `Right` pages: - -~~~~~{.haskell} - match "posts/*" $ do - route $ setExtension ".html" - compile $ pageCompiler - >>> isPagePublished - >>> (applyTemplateCompiler "templates/embargo-post.html" - ||| applyTemplateCompiler "templates/post.html") - >>> applyTemplateCompiler "templates/default.html" - >>> relativizeUrlsCompiler -~~~~~ - -With the conditional rendering in place, the next step is to hide -the unpublished posts from the homepage and the list of posts. -Both lists are generated from the results of a requireAllA call. -The last argument of requireAllA is a Compiler, and requireAllA -passes a pair consisting of the currently rendered page and a list -of all the required pages. All we have to do to suppress the pages -is to write a Compiler that takes such a pair as input, leaves the -first element of the pair unchanged and filters out all the unpublished -pages from list in the second element of the pair and then pass the -output from this compiler to the existing compilers handling the -list generation for the `index` and `posts` pages. - -Again, we can use a function from `Control.Arrow` to build this -compiler from simpler ones, in this case it is `***`, which combines -two arrows to one arrow from pairs to pairs. For our purposes, we -combine the identity arrow, which leaves its input unchanged, and an -ordinary `filter` on a list lifted into the compiler arrow: - -~~~~~{.haskell} -filterPublished :: Compiler (Page a, [Page b]) (Page a, [Page b]) -filterPublished = id *** arr (filter isPublished) -~~~~~ - -All that remains to do is to chain this compiler in front of the existing -compilers passed to requireAllA in the code for `posts.html` - -~~~~~{.haskell} - >>> requireAllA "posts/*" (filterPublished >>> addPostList) -~~~~~ - -and for `index.html`: - -~~~~~{.haskell} - >>> requireAllA "posts/*" - (filterPublished - >>> (id *** arr (take 3 . reverse . sortByBaseName)) - >>> addPostList) -~~~~~ - -You may have noticed that the code for the index page uses the same -`id *** something` construct to extract some elements from the list -of all posts. - -## Don't generate unpublished pages at all - -The above code will treat unpublished posts differently and hide them -from all lists of posts, but they will still be generated, and someone -who knows their URLs will still be able to access them. That may be -what you need, but sometimes you might want to suppress them -completely. The simplest way to do so is to leave the rendering -pipeline for `"posts/*"` unchanged and just add the `isPagePublished` -compiler at the end. This will not compile, since hakyll knows how to -write a `Page String`, but not how to write an `Either (Page String) -(Page String)`. But that can be amended by a simple type class -declaration: - -~~~~~{.haskell} -instance Writable b => Writable (Either a b) where - write p (Right b) = write p b - write _ _ = return () -~~~~~ - -Now hakyll will happily generate published pages and ignore -unpublished ones. This solution is of course slightly wasteful, as at -will apply all the templates to an unpublished page before finally -discarding it. You can avoid this by using the `+++` function, which -does for the sum datatype `Either` what `***` does for the product -type pair: - -~~~~~{.haskell} - match "posts/*" $ do - route $ setExtension ".html" - compile $ pageCompiler - >>> isPagePublished - >>> (id +++ (applyTemplateCompiler "templates/post.html" - >>> applyTemplateCompiler "templates/default.html" - >>> relativizeUrlsCompiler)) -~~~~~ - -The other problem with this solution is more severe: hakyll will no -longer generate the index and posts pages due to a rare problem in -haskell land: a runtime type error. Hakyll tries to be smart and reuse -the parsed pages from the `match "posts/*"` when processing the -`requireAllA "posts/*"` calls by caching them. But the compilers there -still expect a list of pages instead of a list of eithers, so we have -to replace `filterPublised` with something that works on the -latter. Luckily (or, probably, by design), `Data.Either` provides just -the function we need, so the new filtering compiler is actually -shorter that the original, even though it has a more intimidating -type: - -~~~~~{.haskell} -filterPublishedE :: Compiler (Page a, [Either (Page b) (Page b)]) (Page a, [Page b]) -filterPublishedE = id *** arr rights -~~~~~ - -## Timed releases - -Exploiting the fact that compilers are arrows, we can do more mixing -and matching of compilers to further refine how hakyll deals with page -attributes like `published`. Maybe you want `cron` to update your blog -while you are on vacation, so you want posts to be considered -published if the `published` field is either `true` or a time in the -past. - -If you happen to live in the UK in winter or enjoy to do time zone -calculation in your head, your new function to test if a page is -published and the compiler derived from it might then look like this - -~~~~~{.haskell} -isPublishedYet :: Page a -> UTCTime -> Bool -isPublishedYet page time = - let published = getField "published" page in - published == "true" || after published - where - after published = - let publishAt = parseTime defaultTimeLocale "%Y-%m-%d %H:%M" published in - fromMaybe False (fmap (\embargo -> embargo < time) publishAt) - -isPagePublishedYet :: Compiler (Page a, UTCTime) (Either (Page a) (Page a)) -isPagePublishedYet = arr (\(p,t) -> if isPublishedYet p t then Right p else Left p) -~~~~~ - -This compiler has a pair of a page and a time as its input, and we can -use yet another function from `Control.Arrow` to construct a compiler -that generates the input for it, the function `&&&`. It takes two -compilers (arrows) with the same input type and constructs a compiler -from that type to a pair of the output types of the two compilers. -For the first argument we take the `pageCompiler` which we already -call at the beginning of the page compilation. The second argument -should be a compiler with the same input type as `pageCompiler` that -returns the current time. But the current time lives in the IO monad -and does not at all depend on the resource the current page is -generated from, so we have to cheat a little bit by calling -`unsafeCompiler` with a function that ignores its argument and returns -an `IO UTCTime`, which `unsafeCompiler` will unwrap for us: - -~~~~~{.haskell} - match "posts/*" $ do - route $ setExtension ".html" - compile $ (pageCompiler &&& (unsafeCompiler (\_ -> getCurrentTime))) - >>> isPagePublishedYet - >>> (id +++ ( ... as above ...)) -~~~~~ - -This is all we have to change if we don't generate unpublished pages -at all. If we just hide them from the lists, the call to `|||` -discards the information that a page is not (yet) published that was -encoded in the `Either`. In that case we could use the `setField` -function from `Hakyll.Web.Page.Metadata` to rewrite the `published` -field of the left and right pages in `isPagePublished(Yet)` to -canonical values that the original `isPublished` function called from -`filterPublished` understands: - -~~~~~{.haskell} -isPagePublishedYet = arr (\(p,t) -> if isPublishedYet p t then pub p else unpub p) - where - pub p = Right $ setField "published" "true" p - unpub p = Left $ setField "published" "false" p -~~~~~ - -The final version of this code can be found in the timedblog example, -together with the required `import` statements. diff --git a/web/tutorials/04-compilers.markdown b/web/tutorials/04-compilers.markdown new file mode 100644 index 0000000..a344c6e --- /dev/null +++ b/web/tutorials/04-compilers.markdown @@ -0,0 +1,38 @@ +--- +title: More on compilers: load, and templates +author: Jasper Van der Jeugt +--- + +Loading items +------------- + +The compiler Monad is a complex beast, but this is nicely hidden for the user of +the Hakyll library. + +Suppose that you're generating `index.html` which shows your latest brilliant +blogpost. This requires `posts/foo.markdown` to be generated *before* +`index.html` (so we don't have to generate it twice). But you don't have to care +about all of that: Hakyll will sort this out this out for you automatically! + +Let's see some quick examples. We can load a specific item: + +```haskell +load "posts/foo.markdown" :: Compiler (Item String) +``` + +Or a whole bunch of them: + +```haskell +loadAll "posts/*" :: Compiler [Item String] +``` + +Sometimes you just want the *contents* and not the `Item`: + +```haskell +loadBody "posts/foo.markdown" :: Compiler String +``` + +This is all useful if we want to use Hakyll's templating system. + +Basic templates +--------------- diff --git a/web/tutorials/05-arrows.markdown b/web/tutorials/05-arrows.markdown new file mode 100644 index 0000000..3f2e244 --- /dev/null +++ b/web/tutorials/05-arrows.markdown @@ -0,0 +1,223 @@ +--- +title: Arrow Magic: Metadata Dependent Page Generation +author: Florian Hars +--- + +## Supporting a "published: false" attribute on pages + +Many content management systems or blog platforms support +some kind of workflow that display articles differently or +not at all depending on which state the article is in, for +example whether it has a "published" attribute or not. +Hakyll has no built-in support for anything like this, but since +its compilers are just arrows, it is easy to implement arbitrary +metadata dependent behaviour for rendering pages. + +Let's start by adding support for a "published" attributes to the +`simpleblog` example. We want to consider a blog post published if it +has a `published` metadata element that does not have the value +`false`. A function to test for this is simple: + +~~~~~{.haskell} +isPublished :: Page a -> Bool +isPublished p = + let published = getField "published" p in + published /= "" && published /= "false" +~~~~~ + +The next step is to write a function that tags a page with its +published status, which can be either unpublished or published, using +the standard `Either` datatype and then transform this function +into a `Compiler`. The latter can be done with the standard `arr` +function from `Control.Arrow`, which lifts a function into an arrow: + +~~~~~{.haskell} +isPagePublished :: Compiler (Page a) (Either (Page a) (Page a)) +isPagePublished = arr (\p -> if isPublished p then Right p else Left p) +~~~~~ + +For the next processing steps we now need a compiler that takes an +`Either (Page a) (Page a)` instead of the usual `Page a` as an +input. But the former can be built up from the latter using some +standard combinators from the `Control.Arrow` library. The simplest +one is `|||`, which takes two compilers (arrows) with the same output +type and returns a new compiler that takes an `Either` of the input +types of the Compilers as an input. Maybe we just want to render our +unpublished posts with a big warning that they are provisional, so we +just want to render the unpublished `Left` pages with another template +than the published `Right` pages: + +~~~~~{.haskell} + match "posts/*" $ do + route $ setExtension ".html" + compile $ pageCompiler + >>> isPagePublished + >>> (applyTemplateCompiler "templates/embargo-post.html" + ||| applyTemplateCompiler "templates/post.html") + >>> applyTemplateCompiler "templates/default.html" + >>> relativizeUrlsCompiler +~~~~~ + +With the conditional rendering in place, the next step is to hide +the unpublished posts from the homepage and the list of posts. +Both lists are generated from the results of a requireAllA call. +The last argument of requireAllA is a Compiler, and requireAllA +passes a pair consisting of the currently rendered page and a list +of all the required pages. All we have to do to suppress the pages +is to write a Compiler that takes such a pair as input, leaves the +first element of the pair unchanged and filters out all the unpublished +pages from list in the second element of the pair and then pass the +output from this compiler to the existing compilers handling the +list generation for the `index` and `posts` pages. + +Again, we can use a function from `Control.Arrow` to build this +compiler from simpler ones, in this case it is `***`, which combines +two arrows to one arrow from pairs to pairs. For our purposes, we +combine the identity arrow, which leaves its input unchanged, and an +ordinary `filter` on a list lifted into the compiler arrow: + +~~~~~{.haskell} +filterPublished :: Compiler (Page a, [Page b]) (Page a, [Page b]) +filterPublished = id *** arr (filter isPublished) +~~~~~ + +All that remains to do is to chain this compiler in front of the existing +compilers passed to requireAllA in the code for `posts.html` + +~~~~~{.haskell} + >>> requireAllA "posts/*" (filterPublished >>> addPostList) +~~~~~ + +and for `index.html`: + +~~~~~{.haskell} + >>> requireAllA "posts/*" + (filterPublished + >>> (id *** arr (take 3 . reverse . sortByBaseName)) + >>> addPostList) +~~~~~ + +You may have noticed that the code for the index page uses the same +`id *** something` construct to extract some elements from the list +of all posts. + +## Don't generate unpublished pages at all + +The above code will treat unpublished posts differently and hide them +from all lists of posts, but they will still be generated, and someone +who knows their URLs will still be able to access them. That may be +what you need, but sometimes you might want to suppress them +completely. The simplest way to do so is to leave the rendering +pipeline for `"posts/*"` unchanged and just add the `isPagePublished` +compiler at the end. This will not compile, since hakyll knows how to +write a `Page String`, but not how to write an `Either (Page String) +(Page String)`. But that can be amended by a simple type class +declaration: + +~~~~~{.haskell} +instance Writable b => Writable (Either a b) where + write p (Right b) = write p b + write _ _ = return () +~~~~~ + +Now hakyll will happily generate published pages and ignore +unpublished ones. This solution is of course slightly wasteful, as at +will apply all the templates to an unpublished page before finally +discarding it. You can avoid this by using the `+++` function, which +does for the sum datatype `Either` what `***` does for the product +type pair: + +~~~~~{.haskell} + match "posts/*" $ do + route $ setExtension ".html" + compile $ pageCompiler + >>> isPagePublished + >>> (id +++ (applyTemplateCompiler "templates/post.html" + >>> applyTemplateCompiler "templates/default.html" + >>> relativizeUrlsCompiler)) +~~~~~ + +The other problem with this solution is more severe: hakyll will no +longer generate the index and posts pages due to a rare problem in +haskell land: a runtime type error. Hakyll tries to be smart and reuse +the parsed pages from the `match "posts/*"` when processing the +`requireAllA "posts/*"` calls by caching them. But the compilers there +still expect a list of pages instead of a list of eithers, so we have +to replace `filterPublised` with something that works on the +latter. Luckily (or, probably, by design), `Data.Either` provides just +the function we need, so the new filtering compiler is actually +shorter that the original, even though it has a more intimidating +type: + +~~~~~{.haskell} +filterPublishedE :: Compiler (Page a, [Either (Page b) (Page b)]) (Page a, [Page b]) +filterPublishedE = id *** arr rights +~~~~~ + +## Timed releases + +Exploiting the fact that compilers are arrows, we can do more mixing +and matching of compilers to further refine how hakyll deals with page +attributes like `published`. Maybe you want `cron` to update your blog +while you are on vacation, so you want posts to be considered +published if the `published` field is either `true` or a time in the +past. + +If you happen to live in the UK in winter or enjoy to do time zone +calculation in your head, your new function to test if a page is +published and the compiler derived from it might then look like this + +~~~~~{.haskell} +isPublishedYet :: Page a -> UTCTime -> Bool +isPublishedYet page time = + let published = getField "published" page in + published == "true" || after published + where + after published = + let publishAt = parseTime defaultTimeLocale "%Y-%m-%d %H:%M" published in + fromMaybe False (fmap (\embargo -> embargo < time) publishAt) + +isPagePublishedYet :: Compiler (Page a, UTCTime) (Either (Page a) (Page a)) +isPagePublishedYet = arr (\(p,t) -> if isPublishedYet p t then Right p else Left p) +~~~~~ + +This compiler has a pair of a page and a time as its input, and we can +use yet another function from `Control.Arrow` to construct a compiler +that generates the input for it, the function `&&&`. It takes two +compilers (arrows) with the same input type and constructs a compiler +from that type to a pair of the output types of the two compilers. +For the first argument we take the `pageCompiler` which we already +call at the beginning of the page compilation. The second argument +should be a compiler with the same input type as `pageCompiler` that +returns the current time. But the current time lives in the IO monad +and does not at all depend on the resource the current page is +generated from, so we have to cheat a little bit by calling +`unsafeCompiler` with a function that ignores its argument and returns +an `IO UTCTime`, which `unsafeCompiler` will unwrap for us: + +~~~~~{.haskell} + match "posts/*" $ do + route $ setExtension ".html" + compile $ (pageCompiler &&& (unsafeCompiler (\_ -> getCurrentTime))) + >>> isPagePublishedYet + >>> (id +++ ( ... as above ...)) +~~~~~ + +This is all we have to change if we don't generate unpublished pages +at all. If we just hide them from the lists, the call to `|||` +discards the information that a page is not (yet) published that was +encoded in the `Either`. In that case we could use the `setField` +function from `Hakyll.Web.Page.Metadata` to rewrite the `published` +field of the left and right pages in `isPagePublished(Yet)` to +canonical values that the original `isPublished` function called from +`filterPublished` understands: + +~~~~~{.haskell} +isPagePublishedYet = arr (\(p,t) -> if isPublishedYet p t then pub p else unpub p) + where + pub p = Right $ setField "published" "true" p + unpub p = Left $ setField "published" "false" p +~~~~~ + +The final version of this code can be found in the timedblog example, +together with the required `import` statements. diff --git a/web/tutorials/05-guide.markdown b/web/tutorials/05-guide.markdown deleted file mode 100644 index e8c5800..0000000 --- a/web/tutorials/05-guide.markdown +++ /dev/null @@ -1,129 +0,0 @@ ---- -title: A Guide to the Hakyll Module Zoo -author: Brent Yorgey ---- - -The hakyll package [contains a bewildering array](/reference/) of -modules, and it's hard to know where to look when you are just getting -started---especially since many of them are only used by hakyll -internally and not that useful to website authors. This guide -provides a quick reference to the contents of the most important and -useful modules. - -## Core modules - -These are the modules containing the fundamental tools and concepts -you need to get started building a site with hakyll. - -* [Hakyll.Core.Compiler](/reference/Hakyll-Core-Compiler.html) - - This is one of the modules you should look at first. It defines the - fundamental `Compiler` type and has a bunch of documentation explaining how to - use `Compiler`s and their `Arrow`-based interface. - - It also defines many primitive `Compiler`s as well as several - variants on `require`, which allow you to bring together multiple - resources to create a single output. - -* [Hakyll.Core.Routes](/reference/Hakyll-Core-Routes.html) - - Specify where compiled items should go in the output site. - -* [Hakyll.Core.Rules](/reference/Hakyll-Core-Rules.html) - - Specify which compilers and routes should be used on which - resources. - - Also has combinators for grouping (necessary if you want to use - the same resources for multiple purposes), creating outputs that - are not based on any resources, and even dynamically generating - compilers. - -* [Hakyll.Core.Identifier.Pattern](/reference/Hakyll-Core-Identifier-Pattern.html) - - Combinators for creating *patterns*, i.e. predicates that pick out - a set of resources: filesystem globs, arbitrary predicates, - explicit lists, regular expressions, and so on. - -* [Hakyll.Web.Page](/reference/Hakyll-Web-Page.html) - - A `Page`, consisting of some metadata and a body, is one of the - most fundamental structures used by hakyll. This module has some - documentation explaining how pages work, and defines a number of - compilers and utilities for working with them. - -* [Hakyll.Web.Page.Metadata](/reference/Hakyll-Web-Page-Metadata.html) - - Utilities for manipulating page metadata. - -* [Hakyll.Web.Template](/reference/Hakyll-Web-Template.html) - - Templates specify how to take the content of a `Page` and turn - it into HTML output. - -* [Hakyll.Main](/reference/Hakyll-Main.html) - - The main `hakyll` function that runs the whole show. There is - also a `hakyllWith` function which allows for a custom - configuration. - -## Pre-packaged solutions - -These modules contain some "pre-packaged" solutions for common -situations. They can be used as-is, or their source can be used as -inspiration for your own tools. - -* [Hakyll.Web.Page.List](/reference/Hakyll-Web-Page-List.html) - - Combine several pages into a list, such as a list of posts, images - in a gallery, etc. - -* [Hakyll.Web.Feed](/reference/Hakyll-Web-Feed.html) - - Create RSS feeds. - -* [Hakyll.Web.Tags](/reference/Hakyll-Web-Tags.html) - - Work with tags and categories. - -## Useful utilities - -* [Hakyll.Core.UnixFilter](/reference/Hakyll-Core-UnixFilter.html) - - Use any unix utility as a compiler. - -* [Hakyll.Core.Util.Arrow](/reference/Hakyll-Core-Util-Arrow.html) - - A few utilities for working with arrows, including a constant - arrow, unit arrow, and running an entire list of arrows on a - single input. - -* [Hakyll.Core.Util.File](/reference/Hakyll-Core-Util-File.html) - - Utility functions for working with the file system. - -* [Hakyll.Core.Util.String](/reference/Hakyll-Core-Util-String.html) - - A few utility functions for working with `String`s (trim spaces, - find and replace, split on regexp). - -* [Hakyll.Core.Writable.WritableTuple](/reference/Hakyll-Core-Writable-WritableTuple.html) - - A utility type covering the situation where you generate some - data, some of which you want to write to disk, and some of which - you don't want to write but will be needed in order to generate - something else later. - -## Advanced customization - -* [Hakyll.Core.Writable](/reference/Hakyll-Core-Writable.html) - - The `Writable` class is for resources which can be written to disk - as part of the output site. You can specify how to write your own - custom types to disk by giving them a `Writable` instance. - -* [Hakyll.Web.Pandoc](/reference/Hakyll-Web-Pandoc.html) - - Some compilers for running pandoc. Normally they are run - automatically as part of, for example, `pageCompiler`; but - sometimes it is useful to be able to run pandoc explicitly. diff --git a/web/tutorials/06-guide.markdown b/web/tutorials/06-guide.markdown new file mode 100644 index 0000000..e8c5800 --- /dev/null +++ b/web/tutorials/06-guide.markdown @@ -0,0 +1,129 @@ +--- +title: A Guide to the Hakyll Module Zoo +author: Brent Yorgey +--- + +The hakyll package [contains a bewildering array](/reference/) of +modules, and it's hard to know where to look when you are just getting +started---especially since many of them are only used by hakyll +internally and not that useful to website authors. This guide +provides a quick reference to the contents of the most important and +useful modules. + +## Core modules + +These are the modules containing the fundamental tools and concepts +you need to get started building a site with hakyll. + +* [Hakyll.Core.Compiler](/reference/Hakyll-Core-Compiler.html) + + This is one of the modules you should look at first. It defines the + fundamental `Compiler` type and has a bunch of documentation explaining how to + use `Compiler`s and their `Arrow`-based interface. + + It also defines many primitive `Compiler`s as well as several + variants on `require`, which allow you to bring together multiple + resources to create a single output. + +* [Hakyll.Core.Routes](/reference/Hakyll-Core-Routes.html) + + Specify where compiled items should go in the output site. + +* [Hakyll.Core.Rules](/reference/Hakyll-Core-Rules.html) + + Specify which compilers and routes should be used on which + resources. + + Also has combinators for grouping (necessary if you want to use + the same resources for multiple purposes), creating outputs that + are not based on any resources, and even dynamically generating + compilers. + +* [Hakyll.Core.Identifier.Pattern](/reference/Hakyll-Core-Identifier-Pattern.html) + + Combinators for creating *patterns*, i.e. predicates that pick out + a set of resources: filesystem globs, arbitrary predicates, + explicit lists, regular expressions, and so on. + +* [Hakyll.Web.Page](/reference/Hakyll-Web-Page.html) + + A `Page`, consisting of some metadata and a body, is one of the + most fundamental structures used by hakyll. This module has some + documentation explaining how pages work, and defines a number of + compilers and utilities for working with them. + +* [Hakyll.Web.Page.Metadata](/reference/Hakyll-Web-Page-Metadata.html) + + Utilities for manipulating page metadata. + +* [Hakyll.Web.Template](/reference/Hakyll-Web-Template.html) + + Templates specify how to take the content of a `Page` and turn + it into HTML output. + +* [Hakyll.Main](/reference/Hakyll-Main.html) + + The main `hakyll` function that runs the whole show. There is + also a `hakyllWith` function which allows for a custom + configuration. + +## Pre-packaged solutions + +These modules contain some "pre-packaged" solutions for common +situations. They can be used as-is, or their source can be used as +inspiration for your own tools. + +* [Hakyll.Web.Page.List](/reference/Hakyll-Web-Page-List.html) + + Combine several pages into a list, such as a list of posts, images + in a gallery, etc. + +* [Hakyll.Web.Feed](/reference/Hakyll-Web-Feed.html) + + Create RSS feeds. + +* [Hakyll.Web.Tags](/reference/Hakyll-Web-Tags.html) + + Work with tags and categories. + +## Useful utilities + +* [Hakyll.Core.UnixFilter](/reference/Hakyll-Core-UnixFilter.html) + + Use any unix utility as a compiler. + +* [Hakyll.Core.Util.Arrow](/reference/Hakyll-Core-Util-Arrow.html) + + A few utilities for working with arrows, including a constant + arrow, unit arrow, and running an entire list of arrows on a + single input. + +* [Hakyll.Core.Util.File](/reference/Hakyll-Core-Util-File.html) + + Utility functions for working with the file system. + +* [Hakyll.Core.Util.String](/reference/Hakyll-Core-Util-String.html) + + A few utility functions for working with `String`s (trim spaces, + find and replace, split on regexp). + +* [Hakyll.Core.Writable.WritableTuple](/reference/Hakyll-Core-Writable-WritableTuple.html) + + A utility type covering the situation where you generate some + data, some of which you want to write to disk, and some of which + you don't want to write but will be needed in order to generate + something else later. + +## Advanced customization + +* [Hakyll.Core.Writable](/reference/Hakyll-Core-Writable.html) + + The `Writable` class is for resources which can be written to disk + as part of the output site. You can specify how to write your own + custom types to disk by giving them a `Writable` instance. + +* [Hakyll.Web.Pandoc](/reference/Hakyll-Web-Pandoc.html) + + Some compilers for running pandoc. Normally they are run + automatically as part of, for example, `pageCompiler`; but + sometimes it is useful to be able to run pandoc explicitly. -- cgit v1.2.3