summaryrefslogtreecommitdiff
path: root/src/Hakyll/Web/Page.hs
blob: 14507029409515081231bbf5e541ec6eb4ed98de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
-- | A page is a key-value mapping, representing a page on your site
--
-- A page is an important concept in Hakyll. It is a key-value mapping, and has
-- one field with an arbitrary type. A 'Page' thus consists of
--
-- * a key-value mapping (of the type @Map String String@);
--
-- * a value (of the type @a@).
--
-- Usually, the value will be a 'String' as well, and the value will be the body
-- of the page.
--
-- Pages can be constructed using Haskell, but they are usually parsed from a
-- file. The file format for pages is pretty straightforward.
--
-- > This is a simple page
-- > consisting of two lines.
--
-- This is a valid page with two lines. If we load this in Hakyll, there would
-- be no metadata, and the body would be the given text. Let's look at a page
-- with some metadata.
--
-- > ---
-- > title: Alice's Adventures in Wonderland
-- > author: Lewis Caroll
-- > year: 1865
-- > ---
-- >
-- > Chapter I
-- > =========
-- >
-- > Down the Rabbit-Hole
-- > --------------------
-- >
-- > Alice was beginning to get very tired of sitting by her sister on the bank,
-- > and of having nothing to do: once or twice she had peeped into the book her
-- > sister was reading, but it had no pictures or conversations in it, "and
-- > what is the use of a book," thought Alice "without pictures or
-- > conversation?"
-- >
-- > ...
--
-- As you can see, we construct a metadata header in Hakyll using @---@. Then,
-- we simply list all @key: value@ pairs, and end with @---@ again. This page
-- contains three metadata fields and a body. The body is given in markdown
-- format, which can be easily rendered to HTML by Hakyll, using pandoc.
--
{-# LANGUAGE DeriveDataTypeable #-}
module Hakyll.Web.Page
    ( Page (..)
    , fromBody
    , fromMap
    , toMap
    , readPageCompiler
    , pageCompiler
    , pageCompilerWith
    , pageCompilerWithPandoc
    , addDefaultFields
    , sortByBaseName
    ) where

import Prelude hiding (id)
import Control.Category (id)
import Control.Arrow (arr, (>>^), (&&&), (>>>))
import System.FilePath (takeBaseName, takeDirectory)
import qualified Data.Map as M
import Data.List (sortBy)
import Data.Ord (comparing)

import Text.Pandoc (Pandoc, ParserState, WriterOptions)

import Hakyll.Core.Identifier
import Hakyll.Core.Compiler
import Hakyll.Core.Resource
import Hakyll.Web.Page.Internal
import Hakyll.Web.Page.Read
import Hakyll.Web.Page.Metadata
import Hakyll.Web.Pandoc
import Hakyll.Web.Template
import Hakyll.Web.Util.Url

-- | Create a page from a body, without metadata
--
fromBody :: a -> Page a
fromBody = Page M.empty

-- | Read a page (do not render it)
--
readPageCompiler :: Compiler Resource (Page String)
readPageCompiler = getResourceString >>^ readPage

-- | Read a page, add default fields, substitute fields and render using pandoc
--
pageCompiler :: Compiler Resource (Page String)
pageCompiler =
    pageCompilerWith defaultHakyllParserState defaultHakyllWriterOptions

-- | A version of 'pageCompiler' which allows you to specify your own pandoc
-- options
--
pageCompilerWith :: ParserState -> WriterOptions
                 -> Compiler Resource (Page String)
pageCompilerWith state options = pageCompilerWithPandoc state options id

-- | An extension of 'pageCompilerWith' which allows you to specify a custom
-- pandoc transformer for the content
--
pageCompilerWithPandoc :: ParserState -> WriterOptions
                       -> (Pandoc -> Pandoc)
                       -> Compiler Resource (Page String)
pageCompilerWithPandoc state options f = cached "pageCompilerWithPandoc" $
    readPageCompiler >>> addDefaultFields >>> arr applySelf
                     >>> pageReadPandocWith state
                     >>> arr (fmap (writePandocWith options . f))

-- | Add a number of default metadata fields to a page. These fields include:
--
-- * @$url@
--
-- * @$category@
--
-- * @$title@
--
-- * @$path@
--
addDefaultFields :: Compiler (Page a) (Page a)
addDefaultFields =   (getRoute &&& id >>^ uncurry addRoute)
                 >>> (getIdentifier &&& id >>^ uncurry addIdentifier)
  where
    -- Add root and url, based on route
    addRoute Nothing  = id
    addRoute (Just r) = trySetField "url" (toUrl r)

    -- Add title and category, based on identifier
    addIdentifier i = trySetField "title" (takeBaseName p)
                    . trySetField "category" (takeBaseName $ takeDirectory p)
                    . trySetField "path" p
      where
        p = toFilePath i

-- | Sort posts based on the basename of the post. This is equivalent to a
-- chronologival sort, because of the @year-month-day-title.extension@ naming
-- convention in Hakyll.
--
sortByBaseName :: [Page a] -> [Page a]
sortByBaseName = sortBy $ comparing $ takeBaseName . getField "path"