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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Hakyll.Web.Template.Internal
( Template (..)
, template
, templateBodyCompiler
, templateCompiler
, applyTemplate
, loadAndApplyTemplate
, applyAsTemplate
, readTemplate
, compileTemplateItem
, unsafeReadTemplateFile
, module Hakyll.Web.Template.Internal.Element
, module Hakyll.Web.Template.Internal.Trim
) where
--------------------------------------------------------------------------------
import Control.Monad.Except (catchError)
import Data.Binary (Binary)
import Data.List (intercalate)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Typeable (Typeable)
import GHC.Exts (IsString (..))
import GHC.Generics (Generic)
--------------------------------------------------------------------------------
import Hakyll.Core.Compiler
import Hakyll.Core.Compiler.Internal
import Hakyll.Core.Identifier
import Hakyll.Core.Item
import Hakyll.Core.Writable
import Hakyll.Web.Template.Context
import Hakyll.Web.Template.Internal.Element
import Hakyll.Web.Template.Internal.Trim
--------------------------------------------------------------------------------
-- | Datatype used for template substitutions.
data Template = Template
{ tplElements :: [TemplateElement]
, tplOrigin :: FilePath -- Only for error messages.
} deriving (Show, Eq, Generic, Binary, Typeable)
--------------------------------------------------------------------------------
instance Writable Template where
-- Writing a template is impossible
write _ _ = return ()
--------------------------------------------------------------------------------
instance IsString Template where
fromString = readTemplate
--------------------------------------------------------------------------------
-- | Wrap the constructor to ensure trim is called.
template :: FilePath -> [TemplateElement] -> Template
template p = flip Template p . trim
--------------------------------------------------------------------------------
-- | Parse a string into a template.
-- You should prefer 'compileTemplateItem' over this.
readTemplate :: String -> Template
readTemplate = either error (template origin) . parseTemplateElemsFile origin
where
origin = "{literal}"
{-# DEPRECATED readTemplate "Use templateCompiler instead" #-}
--------------------------------------------------------------------------------
-- | Parse an item body into a template.
-- Provides useful error messages in the 'Compiler' monad.
compileTemplateItem :: Item String -> Compiler Template
compileTemplateItem item = let file = itemIdentifier item
in compileTemplateFile file (itemBody item)
--------------------------------------------------------------------------------
compileTemplateFile :: Identifier -> String -> Compiler Template
compileTemplateFile file = either fail (return . template origin)
. parseTemplateElemsFile origin
where
origin = show file
--------------------------------------------------------------------------------
-- | Read a template, without metadata header
templateBodyCompiler :: Compiler (Item Template)
templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
item <- getResourceBody
file <- getUnderlying
withItemBody (compileTemplateFile file) item
--------------------------------------------------------------------------------
-- | Read complete file contents as a template
templateCompiler :: Compiler (Item Template)
templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
item <- getResourceString
file <- getUnderlying
withItemBody (compileTemplateFile file) item
--------------------------------------------------------------------------------
-- | Interpolate template expressions from context values in a page
applyTemplate :: Template -- ^ Template
-> Context a -- ^ Context
-> Item a -- ^ Page
-> Compiler (Item String) -- ^ Resulting item
applyTemplate tpl context item = do
body <- applyTemplate' (tplElements tpl) context item `catchError` handler
return $ itemSetBody body item
where
tplName = tplOrigin tpl
itemName = show $ itemIdentifier item
handler es = fail $ "Hakyll.Web.Template.applyTemplate: Failed to " ++
(if tplName == itemName
then "interpolate template in item " ++ itemName
else "apply template " ++ tplName ++ " to item " ++ itemName) ++
":\n" ++ intercalate ",\n" es
--------------------------------------------------------------------------------
applyTemplate'
:: forall a.
[TemplateElement] -- ^ Unwrapped Template
-> Context a -- ^ Context
-> Item a -- ^ Page
-> Compiler String -- ^ Resulting item
applyTemplate' tes context x = go tes
where
context' :: String -> [String] -> Item a -> Compiler ContextField
context' = unContext (context `mappend` missingField)
go = fmap concat . mapM applyElem
---------------------------------------------------------------------------
applyElem :: TemplateElement -> Compiler String
applyElem TrimL = trimError
applyElem TrimR = trimError
applyElem (Chunk c) = return c
applyElem (Expr e) = withErrorMessage evalMsg (applyStringExpr typeMsg e)
where
evalMsg = "In expr '$" ++ show e ++ "$'"
typeMsg = "expr '$" ++ show e ++ "$'"
applyElem Escaped = return "$"
applyElem (If e t mf) = compilerTry (applyExpr e) >>= handle
where
f = maybe (return "") go mf
handle (Right _) = go t
handle (Left (CompilationNoResult _)) = f
handle (Left (CompilationFailure es)) = debug (NonEmpty.toList es) >> f
debug = compilerDebugEntries ("Hakyll.Web.Template.applyTemplate: " ++
"[ERROR] in 'if' condition on expr '" ++ show e ++ "':")
applyElem (For e b s) = withErrorMessage headMsg (applyExpr e) >>= \cf -> case cf of
EmptyField -> expected "list" "boolean" typeMsg
StringField _ -> expected "list" "string" typeMsg
ListField c xs -> withErrorMessage bodyMsg $ do
sep <- maybe (return "") go s
bs <- mapM (applyTemplate' b c) xs
return $ intercalate sep bs
where
headMsg = "In expr '$for(" ++ show e ++ ")$'"
typeMsg = "loop expr '" ++ show e ++ "'"
bodyMsg = "In loop context of '$for(" ++ show e ++ ")$'"
applyElem (Partial e) = withErrorMessage headMsg $
applyStringExpr typeMsg e >>= \p ->
withErrorMessage inclMsg $ do
tpl' <- loadBody (fromFilePath p)
itemBody <$> applyTemplate tpl' context x
where
headMsg = "In expr '$partial(" ++ show e ++ ")$'"
typeMsg = "partial expr '" ++ show e ++ "'"
inclMsg = "In inclusion of '$partial(" ++ show e ++ ")$'"
---------------------------------------------------------------------------
applyExpr :: TemplateExpr -> Compiler ContextField
applyExpr (Ident (TemplateKey k)) = context' k [] x
applyExpr (Call (TemplateKey k) args) = do
args' <- mapM (\e -> applyStringExpr (typeMsg e) e) args
context' k args' x
where
typeMsg e = "argument '" ++ show e ++ "'"
applyExpr (StringLiteral s) = return (StringField s)
----------------------------------------------------------------------------
applyStringExpr :: String -> TemplateExpr -> Compiler String
applyStringExpr msg expr =
applyExpr expr >>= getString
where
getString EmptyField = expected "string" "boolean" msg
getString (StringField s) = return s
getString (ListField _ _) = expected "string" "list" msg
expected typ act expr = fail $ unwords ["Hakyll.Web.Template.applyTemplate:",
"expected", typ, "but got", act, "for", expr]
-- expected to never happen with all templates constructed by 'template'
trimError = fail $
"Hakyll.Web.Template.applyTemplate: template not fully trimmed."
--------------------------------------------------------------------------------
-- | The following pattern is so common:
--
-- > tpl <- loadBody "templates/foo.html"
-- > someCompiler
-- > >>= applyTemplate tpl context
--
-- That we have a single function which does this:
--
-- > someCompiler
-- > >>= loadAndApplyTemplate "templates/foo.html" context
loadAndApplyTemplate :: Identifier -- ^ Template identifier
-> Context a -- ^ Context
-> Item a -- ^ Page
-> Compiler (Item String) -- ^ Resulting item
loadAndApplyTemplate identifier context item = do
tpl <- loadBody identifier
applyTemplate tpl context item
--------------------------------------------------------------------------------
-- | It is also possible that you want to substitute @$key$@s within the body of
-- an item. This function does that by interpreting the item body as a template,
-- and then applying it to itself.
applyAsTemplate :: Context String -- ^ Context
-> Item String -- ^ Item and template
-> Compiler (Item String) -- ^ Resulting item
applyAsTemplate context item = do
tpl <- compileTemplateItem item
applyTemplate tpl context item
--------------------------------------------------------------------------------
unsafeReadTemplateFile :: FilePath -> Compiler Template
unsafeReadTemplateFile file = do
tpl <- unsafeCompiler $ readFile file
compileTemplateFile (fromFilePath file) tpl
{-# DEPRECATED unsafeReadTemplateFile "Use templateCompiler" #-}
|