summaryrefslogtreecommitdiff
path: root/src/Text/Hakyll/Renderables.hs
blob: 37bd52104d5ad823a4f9c73ce2459b9c0fb2ce39 (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
module Text.Hakyll.Renderables
    ( createCustomPage
    , createListing
    , createListingWith
    , PagePath
    , createPagePath
    , CombinedRenderable
    , combine
    , combineWithUrl
    ) where

import qualified Data.Map as M
import Control.Arrow (second)
import Control.Monad (liftM2, mplus)
import Control.Applicative ((<$>))


import Text.Hakyll.File
import Text.Hakyll.Context
import Text.Hakyll.RenderAction
import Text.Hakyll.Render
import Text.Hakyll.Internal.Page

-- | Create a custom page.
--   
--   The association list given maps keys to values for substitution. Note
--   that as value, you can either give a @String@ or a @Hakyll String@.
--   A @Hakyll String@ is preferred for more complex data, since it allows
--   dependency checking. A @String@ is obviously more simple to use in some
--   cases.
createCustomPage :: String
                 -> [FilePath]
                 -> [(String, Either String (RenderAction () String))]
                 -> RenderAction () Context
createCustomPage url dependencies association = RenderAction
    { actionDependencies = dependencies
    , actionUrl          = Just $ return url
    , actionFunction     = \_ -> M.fromList <$> assoc'
    }
  where
    mtuple (a, b) = b >>= \b' -> return (a, b')
    toHakyllString = second (either return runRenderAction)
    assoc' = mapM (mtuple . toHakyllString) association

-- | A @createCustomPage@ function specialized in creating listings.
--
--   This function creates a listing of a certain list of @Renderable@s. Every
--   item in the list is created by applying the given template to every
--   renderable. You can also specify additional context to be included in the
--   @CustomPage@.
--
--   > let customPage = createListingWith 
--   >                      "index.html" -- Destination of the page.
--   >                      "templates/postitem.html" -- Path to template to
--   >                                                -- render the items with.
--   >                      posts -- ^ Renderables to create the list with.
--   >                      [("title", "Home")] -- ^ Additional context
createListing :: String -- ^ Destination of the page.
              -> FilePath -- ^ Template to render all items with.
              -> [RenderAction () Context] -- ^ Renderables in the list.
              -> [(String, String)] -- ^ Additional context.
              -> RenderAction () Context
createListing = createListingWith id

-- | A @createCustomPage@ function specialized in creating listings.
--
--   In addition to @createListing@, this function allows you to specify an
--   extra @ContextManipulation@ for all @Renderable@s given.
createListingWith :: ContextManipulation -- ^ Manipulation for the renderables.
                  -> String -- ^ Destination of the page.
                  -> FilePath -- ^ Template to render all items with.
                  -> [RenderAction () Context] -- ^ Renderables in the list.
                  -> [(String, String)] -- ^ Additional context.
                  -> RenderAction () Context
createListingWith manipulation url template renderables additional =
    createCustomPage url dependencies context
  where
    dependencies = template : concatMap actionDependencies renderables
    context = ("body", Right concatenation) : additional'
    concatenation = renderAndConcatWith manipulation [template] renderables
    additional' = map (second Left) additional

-- | PagePath is a class that wraps a FilePath. This is used to render Pages
--   without reading them first through use of caching.
newtype PagePath = PagePath FilePath
                 deriving (Ord, Eq, Read, Show)

-- | Create a PagePath from a FilePath.
createPagePath :: FilePath -> RenderAction () Context
createPagePath path = RenderAction
    { actionDependencies = [path]
    , actionUrl          = Just $ toUrl path
    , actionFunction     = const (readPage path)
    }

-- | A combination of two other renderables.
data CombinedRenderable a b = CombinedRenderable a b
                            | CombinedRenderableWithUrl FilePath a b
                            deriving (Ord, Eq, Read, Show)

-- | Combine two renderables. The url will always be taken from the first
--   @Renderable@. Also, if a `$key` is present in both renderables, the
--   value from the first @Renderable@ will be taken as well.
--
--   Since renderables are always more or less key-value maps, you can see
--   this as a @union@ between two maps.
combine :: RenderAction () Context -> RenderAction () Context
        -> RenderAction () Context
combine x y = RenderAction
    { actionDependencies = actionDependencies x ++ actionDependencies y
    , actionUrl          = actionUrl x `mplus` actionUrl y
    , actionFunction     = \_ ->
        liftM2 (M.union) (runRenderAction x) (runRenderAction y)
    }

-- | Combine two renderables and set a custom URL. This behaves like @combine@,
--   except that for the @url@ field, the given URL is always chosen.
combineWithUrl :: FilePath
               -> RenderAction () Context
               -> RenderAction () Context
               -> RenderAction () Context
combineWithUrl url x y = combine'
    { actionUrl          = Just $ return url
    , actionFunction     = \_ ->
        (M.insert "url" url) <$> runRenderAction combine'
    }
  where
    combine' = combine x y