on master help sign in
Reorganise template module hierarchy
commit82ba9542e75238f4b69d1a497d429962cdff1e14 authorJasper Van der Jeugt <m@jaspervdj.be> dateWed 3 Aug 2016, 12:14 (10 years ago) parent98e0b03
5 files changed+451−430
hakyll.cabal+3−2 src/Hakyll/Web/Template.hs−184 src/Hakyll/Web/Template/Internal.hs+140−234 src/Hakyll/Web/Template/Internal/Element.hs+298 src/Hakyll/Web/Template/Trim.hs → src/Hakyll/Web/Template/Internal/Trim.hs+10−10
hakyll.cabal+3−2
@@ -116,9 +116,10 @@ Library
116116 Hakyll.Web.Tags
117117 Hakyll.Web.Paginate
118118 Hakyll.Web.Template
119− Hakyll.Web.Template.Internal
120− Hakyll.Web.Template.Trim
121119 Hakyll.Web.Template.Context
120+ Hakyll.Web.Template.Internal
121+ Hakyll.Web.Template.Internal.Element
122+ Hakyll.Web.Template.Internal.Trim
122123 Hakyll.Web.Template.List
123124
124125 Other-Modules:
src/Hakyll/Web/Template.hs−184
@@ -138,8 +138,6 @@
138138-- > 3...2...1
139139-- > </p>
140140--
141−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
142−{-# LANGUAGE ScopedTypeVariables #-}
143141module Hakyll.Web.Template
144142 ( Template
145143 , templateBodyCompiler
@@ -153,186 +151,4 @@ module Hakyll.Web.Template
153151
154152
155153--------------------------------------------------------------------------------
156−import Control.Monad.Except (MonadError (..))
157−import Data.Binary (Binary)
158−import Data.List (intercalate)
159−import Data.Typeable (Typeable)
160−import GHC.Exts (IsString (..))
161−import Prelude hiding (id)
162−
163−
164−--------------------------------------------------------------------------------
165−import Hakyll.Core.Compiler
166−import Hakyll.Core.Identifier
167−import Hakyll.Core.Item
168−import Hakyll.Core.Writable
169−import Hakyll.Web.Template.Context
170154import Hakyll.Web.Template.Internal
171−import Hakyll.Web.Template.Trim
172−
173−
174−--------------------------------------------------------------------------------
175−-- | Datatype used for template substitutions.
176−newtype Template = Template
177− { unTemplate :: [TemplateElement]
178− } deriving (Show, Eq, Binary, Typeable)
179−
180−
181−--------------------------------------------------------------------------------
182−instance Writable Template where
183− -- Writing a template is impossible
184− write _ _ = return ()
185−
186−
187−--------------------------------------------------------------------------------
188−instance IsString Template where
189− fromString = readTemplate
190−
191−
192−--------------------------------------------------------------------------------
193−-- | Wrap the constructor to ensure trim is called.
194−template :: [TemplateElement] -> Template
195−template = Template . trim
196−
197−
198−--------------------------------------------------------------------------------
199−readTemplate :: String -> Template
200−readTemplate = Template . trim . readTemplateElems
201−
202−--------------------------------------------------------------------------------
203−-- | Read a template, without metadata header
204−templateBodyCompiler :: Compiler (Item Template)
205−templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
206− item <- getResourceBody
207− file <- getResourceFilePath
208− return $ fmap (template . readTemplateElemsFile file) item
209−
210−--------------------------------------------------------------------------------
211−-- | Read complete file contents as a template
212−templateCompiler :: Compiler (Item Template)
213−templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
214− item <- getResourceString
215− file <- getResourceFilePath
216− return $ fmap (template . readTemplateElemsFile file) item
217−
218−
219−--------------------------------------------------------------------------------
220−applyTemplate :: Template -- ^ Template
221− -> Context a -- ^ Context
222− -> Item a -- ^ Page
223− -> Compiler (Item String) -- ^ Resulting item
224−applyTemplate tpl context item = do
225− body <- applyTemplate' (unTemplate tpl) context item
226− return $ itemSetBody body item
227−
228−
229−--------------------------------------------------------------------------------
230−applyTemplate'
231− :: forall a.
232− [TemplateElement] -- ^ Unwrapped Template
233− -> Context a -- ^ Context
234− -> Item a -- ^ Page
235− -> Compiler String -- ^ Resulting item
236−applyTemplate' tes context x = go tes
237− where
238− context' :: String -> [String] -> Item a -> Compiler ContextField
239− context' = unContext (context `mappend` missingField)
240−
241− go = fmap concat . mapM applyElem
242−
243− trimError = error $ "Hakyll.Web.Template.applyTemplate: template not " ++
244− "fully trimmed."
245−
246− ---------------------------------------------------------------------------
247−
248− applyElem :: TemplateElement -> Compiler String
249−
250− applyElem TrimL = trimError
251−
252− applyElem TrimR = trimError
253−
254− applyElem (Chunk c) = return c
255−
256− applyElem (Expr e) = applyExpr e >>= getString e
257−
258− applyElem Escaped = return "$"
259−
260− applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler
261− where
262− handler _ = case mf of
263− Nothing -> return ""
264− Just f -> go f
265−
266− applyElem (For e b s) = applyExpr e >>= \cf -> case cf of
267− StringField _ -> fail $
268− "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
269− "got StringField for expr " ++ show e
270− ListField c xs -> do
271− sep <- maybe (return "") go s
272− bs <- mapM (applyTemplate' b c) xs
273− return $ intercalate sep bs
274−
275− applyElem (Partial e) = do
276− p <- applyExpr e >>= getString e
277− tpl' <- loadBody (fromFilePath p)
278− applyTemplate' tpl' context x
279−
280− ---------------------------------------------------------------------------
281−
282− applyExpr :: TemplateExpr -> Compiler ContextField
283−
284− applyExpr (Ident (TemplateKey k)) = context' k [] x
285−
286− applyExpr (Call (TemplateKey k) args) = do
287− args' <- mapM (\e -> applyExpr e >>= getString e) args
288− context' k args' x
289−
290− applyExpr (StringLiteral s) = return (StringField s)
291−
292− ----------------------------------------------------------------------------
293−
294− getString _ (StringField s) = return s
295− getString e (ListField _ _) = fail $
296− "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
297− "got ListField for expr " ++ show e
298−
299−
300−--------------------------------------------------------------------------------
301−-- | The following pattern is so common:
302−--
303−-- > tpl <- loadBody "templates/foo.html"
304−-- > someCompiler
305−-- > >>= applyTemplate tpl context
306−--
307−-- That we have a single function which does this:
308−--
309−-- > someCompiler
310−-- > >>= loadAndApplyTemplate "templates/foo.html" context
311−loadAndApplyTemplate :: Identifier -- ^ Template identifier
312− -> Context a -- ^ Context
313− -> Item a -- ^ Page
314− -> Compiler (Item String) -- ^ Resulting item
315−loadAndApplyTemplate identifier context item = do
316− tpl <- loadBody identifier
317− applyTemplate tpl context item
318−
319−
320−--------------------------------------------------------------------------------
321−-- | It is also possible that you want to substitute @$key$@s within the body of
322−-- an item. This function does that by interpreting the item body as a template,
323−-- and then applying it to itself.
324−applyAsTemplate :: Context String -- ^ Context
325− -> Item String -- ^ Item and template
326− -> Compiler (Item String) -- ^ Resulting item
327−applyAsTemplate context item =
328− let tpl = template $ readTemplateElemsFile file (itemBody item)
329− file = toFilePath $ itemIdentifier item
330− in applyTemplate tpl context item
331−
332−
333−--------------------------------------------------------------------------------
334−unsafeReadTemplateFile :: FilePath -> Compiler Template
335−unsafeReadTemplateFile file = do
336− tpl <- unsafeCompiler $ readFile file
337− pure $ template $ readTemplateElemsFile file tpl
338−
src/Hakyll/Web/Template/Internal.hs+140−234
@@ -1,297 +1,203 @@
1−--------------------------------------------------------------------------------
2−-- | Module containing the template data structure
31{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
43module Hakyll.Web.Template.Internal
5− ( TemplateKey (..)
6− , TemplateExpr (..)
7− , TemplateElement (..)
8− , templateElems
9− , readTemplateElems
10− , readTemplateElemsFile
4+ ( Template (..)
5+ , template
6+ , templateBodyCompiler
7+ , templateCompiler
8+ , applyTemplate
9+ , applyTemplate'
10+ , loadAndApplyTemplate
11+ , applyAsTemplate
12+ , readTemplate
13+ , unsafeReadTemplateFile
14+
15+ , module Hakyll.Web.Template.Internal.Element
16+ , module Hakyll.Web.Template.Internal.Trim
1117 ) where
1218
1319
1420--------------------------------------------------------------------------------
15−import Control.Applicative ((<|>))
16−import Control.Monad (void)
17−import Data.Binary (Binary, get, getWord8, put, putWord8)
18−import Data.List (intercalate)
19−import Data.Maybe (isJust)
20−import Data.Typeable (Typeable)
21−import GHC.Exts (IsString (..))
22−import qualified Text.Parsec as P
23−import qualified Text.Parsec.String as P
24−
25−
26−--------------------------------------------------------------------------------
27−import Hakyll.Core.Util.Parser
21+import Control.Monad.Except (MonadError (..))
22+import Data.Binary (Binary)
23+import Data.List (intercalate)
24+import Data.Typeable (Typeable)
25+import GHC.Exts (IsString (..))
26+import Prelude hiding (id)
2827
2928
3029--------------------------------------------------------------------------------
31−newtype TemplateKey = TemplateKey String
32− deriving (Binary, Show, Eq, Typeable)
30+import Hakyll.Core.Compiler
31+import Hakyll.Core.Identifier
32+import Hakyll.Core.Item
33+import Hakyll.Core.Writable
34+import Hakyll.Web.Template.Context
35+import Hakyll.Web.Template.Internal.Element
36+import Hakyll.Web.Template.Internal.Trim
3337
3438
3539--------------------------------------------------------------------------------
36−instance IsString TemplateKey where
37− fromString = TemplateKey
40+-- | Datatype used for template substitutions.
41+newtype Template = Template
42+ { unTemplate :: [TemplateElement]
43+ } deriving (Show, Eq, Binary, Typeable)
3844
3945
4046--------------------------------------------------------------------------------
41−-- | Elements of a template.
42−data TemplateElement
43− = Chunk String
44− | Expr TemplateExpr
45− | Escaped
46− -- expr, then, else
47− | If TemplateExpr [TemplateElement] (Maybe [TemplateElement])
48− -- expr, body, separator
49− | For TemplateExpr [TemplateElement] (Maybe [TemplateElement])
50− -- filename
51− | Partial TemplateExpr
52− | TrimL
53− | TrimR
54− deriving (Show, Eq, Typeable)
47+instance Writable Template where
48+ -- Writing a template is impossible
49+ write _ _ = return ()
5550
5651
5752--------------------------------------------------------------------------------
58−instance Binary TemplateElement where
59− put (Chunk string) = putWord8 0 >> put string
60− put (Expr e) = putWord8 1 >> put e
61− put Escaped = putWord8 2
62− put (If e t f) = putWord8 3 >> put e >> put t >> put f
63− put (For e b s) = putWord8 4 >> put e >> put b >> put s
64− put (Partial e) = putWord8 5 >> put e
65− put TrimL = putWord8 6
66− put TrimR = putWord8 7
67−
68− get = getWord8 >>= \tag -> case tag of
69− 0 -> Chunk <$> get
70− 1 -> Expr <$> get
71− 2 -> pure Escaped
72− 3 -> If <$> get <*> get <*> get
73− 4 -> For <$> get <*> get <*> get
74− 5 -> Partial <$> get
75− 6 -> pure TrimL
76− 7 -> pure TrimR
77− _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
53+instance IsString Template where
54+ fromString = readTemplate
7855
7956
8057--------------------------------------------------------------------------------
81−-- | Expression in a template
82−data TemplateExpr
83− = Ident TemplateKey
84− | Call TemplateKey [TemplateExpr]
85− | StringLiteral String
86− deriving (Eq, Typeable)
87−
88−
89−--------------------------------------------------------------------------------
90−instance Show TemplateExpr where
91− show (Ident (TemplateKey k)) = k
92− show (Call (TemplateKey k) as) =
93− k ++ "(" ++ intercalate ", " (map show as) ++ ")"
94− show (StringLiteral s) = show s
95−
96−
97−--------------------------------------------------------------------------------
98−instance Binary TemplateExpr where
99− put (Ident k) = putWord8 0 >> put k
100− put (Call k as) = putWord8 1 >> put k >> put as
101− put (StringLiteral s) = putWord8 2 >> put s
102−
103− get = getWord8 >>= \tag -> case tag of
104− 0 -> Ident <$> get
105− 1 -> Call <$> get <*> get
106− 2 -> StringLiteral <$> get
107− _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
58+-- | Wrap the constructor to ensure trim is called.
59+template :: [TemplateElement] -> Template
60+template = Template . trim
10861
10962
11063--------------------------------------------------------------------------------
111−readTemplateElems :: String -> [TemplateElement]
112−readTemplateElems = readTemplateElemsFile "{literal}"
113−
64+readTemplate :: String -> Template
65+readTemplate = Template . trim . readTemplateElems
11466
11567--------------------------------------------------------------------------------
116−readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
117−readTemplateElemsFile file input = case P.parse templateElems file input of
118− Left err -> error $ "Cannot parse template: " ++ show err
119− Right t -> t
120−
68+-- | Read a template, without metadata header
69+templateBodyCompiler :: Compiler (Item Template)
70+templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
71+ item <- getResourceBody
72+ file <- getResourceFilePath
73+ return $ fmap (template . readTemplateElemsFile file) item
12174
12275--------------------------------------------------------------------------------
123−templateElems :: P.Parser [TemplateElement]
124−templateElems = mconcat <$> P.many (P.choice [ lift chunk
125− , lift escaped
126− , conditional
127− , for
128− , partial
129− , expr
130− ])
131− where lift = fmap (:[])
76+-- | Read complete file contents as a template
77+templateCompiler :: Compiler (Item Template)
78+templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
79+ item <- getResourceString
80+ file <- getResourceFilePath
81+ return $ fmap (template . readTemplateElemsFile file) item
13282
13383
13484--------------------------------------------------------------------------------
135−chunk :: P.Parser TemplateElement
136−chunk = Chunk <$> P.many1 (P.noneOf "$")
85+applyTemplate :: Template -- ^ Template
86+ -> Context a -- ^ Context
87+ -> Item a -- ^ Page
88+ -> Compiler (Item String) -- ^ Resulting item
89+applyTemplate tpl context item = do
90+ body <- applyTemplate' (unTemplate tpl) context item
91+ return $ itemSetBody body item
13792
13893
13994--------------------------------------------------------------------------------
140−expr :: P.Parser [TemplateElement]
141−expr = P.try $ do
142− trimLExpr <- trimOpen
143− e <- expr'
144− trimRExpr <- trimClose
145− return $ [TrimL | trimLExpr] ++ [Expr e] ++ [TrimR | trimRExpr]
95+applyTemplate'
96+ :: forall a.
97+ [TemplateElement] -- ^ Unwrapped Template
98+ -> Context a -- ^ Context
99+ -> Item a -- ^ Page
100+ -> Compiler String -- ^ Resulting item
101+applyTemplate' tes context x = go tes
102+ where
103+ context' :: String -> [String] -> Item a -> Compiler ContextField
104+ context' = unContext (context `mappend` missingField)
146105
106+ go = fmap concat . mapM applyElem
147107
148−--------------------------------------------------------------------------------
149−expr' :: P.Parser TemplateExpr
150−expr' = stringLiteral <|> call <|> ident
108+ trimError = error $ "Hakyll.Web.Template.applyTemplate: template not " ++
109+ "fully trimmed."
151110
111+ ---------------------------------------------------------------------------
152112
153−--------------------------------------------------------------------------------
154−escaped :: P.Parser TemplateElement
155−escaped = Escaped <$ P.try (P.string "$$")
113+ applyElem :: TemplateElement -> Compiler String
156114
115+ applyElem TrimL = trimError
157116
158−--------------------------------------------------------------------------------
159−trimOpen :: P.Parser Bool
160−trimOpen = do
161− void $ P.char '$'
162− trimLIf <- P.optionMaybe $ P.try (P.char '-')
163− pure $ isJust trimLIf
117+ applyElem TrimR = trimError
164118
119+ applyElem (Chunk c) = return c
165120
166−--------------------------------------------------------------------------------
167−trimClose :: P.Parser Bool
168−trimClose = do
169− trimIfR <- P.optionMaybe $ P.try (P.char '-')
170− void $ P.char '$'
171− pure $ isJust trimIfR
121+ applyElem (Expr e) = applyExpr e >>= getString e
172122
123+ applyElem Escaped = return "$"
173124
174−--------------------------------------------------------------------------------
175−conditional :: P.Parser [TemplateElement]
176−conditional = P.try $ do
177− -- if
178− trimLIf <- trimOpen
179− void $ P.string "if("
180− e <- expr'
181− void $ P.char ')'
182− trimRIf <- trimClose
183− -- then
184− thenBranch <- templateElems
185− -- else
186− elseParse <- opt "else"
187− -- endif
188− trimLEnd <- trimOpen
189− void $ P.string "endif"
190− trimREnd <- trimClose
191−
192− -- As else is optional we need to sort out where any Trim_s need to go.
193− let (thenBody, elseBody) = maybe (thenNoElse, Nothing) thenElse elseParse
194− where thenNoElse =
195− [TrimR | trimRIf] ++ thenBranch ++ [TrimL | trimLEnd]
196−
197− thenElse (trimLElse, elseBranch, trimRElse) = (thenB, elseB)
198− where thenB = [TrimR | trimRIf]
199− ++ thenBranch
200− ++ [TrimL | trimLElse]
201−
202− elseB = Just $ [TrimR | trimRElse]
203− ++ elseBranch
204− ++ [TrimL | trimLEnd]
205−
206− pure $ [TrimL | trimLIf] ++ [If e thenBody elseBody] ++ [TrimR | trimREnd]
125+ applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler
126+ where
127+ handler _ = case mf of
128+ Nothing -> return ""
129+ Just f -> go f
207130
131+ applyElem (For e b s) = applyExpr e >>= \cf -> case cf of
132+ StringField _ -> fail $
133+ "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
134+ "got StringField for expr " ++ show e
135+ ListField c xs -> do
136+ sep <- maybe (return "") go s
137+ bs <- mapM (applyTemplate' b c) xs
138+ return $ intercalate sep bs
208139
209−--------------------------------------------------------------------------------
210−for :: P.Parser [TemplateElement]
211−for = P.try $ do
212− -- for
213− trimLFor <- trimOpen
214− void $ P.string "for("
215− e <- expr'
216− void $ P.char ')'
217− trimRFor <- trimClose
218− -- body
219− bodyBranch <- templateElems
220− -- sep
221− sepParse <- opt "sep"
222− -- endfor
223− trimLEnd <- trimOpen
224− void $ P.string "endfor"
225− trimREnd <- trimClose
226−
227− -- As sep is optional we need to sort out where any Trim_s need to go.
228− let (forBody, sepBody) = maybe (forNoSep, Nothing) forSep sepParse
229− where forNoSep =
230− [TrimR | trimRFor] ++ bodyBranch ++ [TrimL | trimLEnd]
231−
232− forSep (trimLSep, sepBranch, trimRSep) = (forB, sepB)
233− where forB = [TrimR | trimRFor]
234− ++ bodyBranch
235− ++ [TrimL | trimLSep]
236−
237− sepB = Just $ [TrimR | trimRSep]
238− ++ sepBranch
239− ++ [TrimL | trimLEnd]
240−
241− pure $ [TrimL | trimLFor] ++ [For e forBody sepBody] ++ [TrimR | trimREnd]
140+ applyElem (Partial e) = do
141+ p <- applyExpr e >>= getString e
142+ tpl' <- loadBody (fromFilePath p)
143+ applyTemplate' tpl' context x
242144
145+ ---------------------------------------------------------------------------
243146
244−--------------------------------------------------------------------------------
245−partial :: P.Parser [TemplateElement]
246−partial = P.try $ do
247− trimLPart <- trimOpen
248− void $ P.string "partial("
249− e <- expr'
250− void $ P.char ')'
251− trimRPart <- trimClose
147+ applyExpr :: TemplateExpr -> Compiler ContextField
252148
253− pure $ [TrimL | trimLPart] ++ [Partial e] ++ [TrimR | trimRPart]
149+ applyExpr (Ident (TemplateKey k)) = context' k [] x
254150
151+ applyExpr (Call (TemplateKey k) args) = do
152+ args' <- mapM (\e -> applyExpr e >>= getString e) args
153+ context' k args' x
255154
256−--------------------------------------------------------------------------------
257−ident :: P.Parser TemplateExpr
258−ident = P.try $ Ident <$> key
155+ applyExpr (StringLiteral s) = return (StringField s)
259156
157+ ----------------------------------------------------------------------------
260158
261−--------------------------------------------------------------------------------
262−call :: P.Parser TemplateExpr
263−call = P.try $ do
264− f <- key
265− void $ P.char '('
266− P.spaces
267− as <- P.sepBy expr' (P.spaces >> P.char ',' >> P.spaces)
268− P.spaces
269− void $ P.char ')'
270− return $ Call f as
159+ getString _ (StringField s) = return s
160+ getString e (ListField _ _) = fail $
161+ "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
162+ "got ListField for expr " ++ show e
271163
272164
273165--------------------------------------------------------------------------------
274−stringLiteral :: P.Parser TemplateExpr
275−stringLiteral = do
276− void $ P.char '\"'
277− str <- P.many $ do
278− x <- P.noneOf "\""
279− if x == '\\' then P.anyChar else return x
280− void $ P.char '\"'
281− return $ StringLiteral str
166+-- | The following pattern is so common:
167+--
168+-- > tpl <- loadBody "templates/foo.html"
169+-- > someCompiler
170+-- > >>= applyTemplate tpl context
171+--
172+-- That we have a single function which does this:
173+--
174+-- > someCompiler
175+-- > >>= loadAndApplyTemplate "templates/foo.html" context
176+loadAndApplyTemplate :: Identifier -- ^ Template identifier
177+ -> Context a -- ^ Context
178+ -> Item a -- ^ Page
179+ -> Compiler (Item String) -- ^ Resulting item
180+loadAndApplyTemplate identifier context item = do
181+ tpl <- loadBody identifier
182+ applyTemplate tpl context item
282183
283184
284185--------------------------------------------------------------------------------
285−key :: P.Parser TemplateKey
286−key = TemplateKey <$> metadataKey
186+-- | It is also possible that you want to substitute @$key$@s within the body of
187+-- an item. This function does that by interpreting the item body as a template,
188+-- and then applying it to itself.
189+applyAsTemplate :: Context String -- ^ Context
190+ -> Item String -- ^ Item and template
191+ -> Compiler (Item String) -- ^ Resulting item
192+applyAsTemplate context item =
193+ let tpl = template $ readTemplateElemsFile file (itemBody item)
194+ file = toFilePath $ itemIdentifier item
195+ in applyTemplate tpl context item
287196
288197
289198--------------------------------------------------------------------------------
290−opt :: String -> P.Parser (Maybe (Bool, [TemplateElement], Bool))
291−opt clause = P.optionMaybe $ P.try $ do
292− trimL <- trimOpen
293− void $ P.string clause
294− trimR <- trimClose
295− branch <- templateElems
296− pure (trimL, branch, trimR)
199+unsafeReadTemplateFile :: FilePath -> Compiler Template
200+unsafeReadTemplateFile file = do
201+ tpl <- unsafeCompiler $ readFile file
202+ pure $ template $ readTemplateElemsFile file tpl
297203
src/Hakyll/Web/Template/Internal/Element.hsnew file+298
@@ -0,0 +1,298 @@
1+--------------------------------------------------------------------------------
2+-- | Module containing the elements used in a template. A template is generally
3+-- just a list of these elements.
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+module Hakyll.Web.Template.Internal.Element
6+ ( TemplateKey (..)
7+ , TemplateExpr (..)
8+ , TemplateElement (..)
9+ , templateElems
10+ , readTemplateElems
11+ , readTemplateElemsFile
12+ ) where
13+
14+
15+--------------------------------------------------------------------------------
16+import Control.Applicative ((<|>))
17+import Control.Monad (void)
18+import Data.Binary (Binary, get, getWord8, put, putWord8)
19+import Data.List (intercalate)
20+import Data.Maybe (isJust)
21+import Data.Typeable (Typeable)
22+import GHC.Exts (IsString (..))
23+import qualified Text.Parsec as P
24+import qualified Text.Parsec.String as P
25+
26+
27+--------------------------------------------------------------------------------
28+import Hakyll.Core.Util.Parser
29+
30+
31+--------------------------------------------------------------------------------
32+newtype TemplateKey = TemplateKey String
33+ deriving (Binary, Show, Eq, Typeable)
34+
35+
36+--------------------------------------------------------------------------------
37+instance IsString TemplateKey where
38+ fromString = TemplateKey
39+
40+
41+--------------------------------------------------------------------------------
42+-- | Elements of a template.
43+data TemplateElement
44+ = Chunk String
45+ | Expr TemplateExpr
46+ | Escaped
47+ -- expr, then, else
48+ | If TemplateExpr [TemplateElement] (Maybe [TemplateElement])
49+ -- expr, body, separator
50+ | For TemplateExpr [TemplateElement] (Maybe [TemplateElement])
51+ -- filename
52+ | Partial TemplateExpr
53+ | TrimL
54+ | TrimR
55+ deriving (Show, Eq, Typeable)
56+
57+
58+--------------------------------------------------------------------------------
59+instance Binary TemplateElement where
60+ put (Chunk string) = putWord8 0 >> put string
61+ put (Expr e) = putWord8 1 >> put e
62+ put Escaped = putWord8 2
63+ put (If e t f) = putWord8 3 >> put e >> put t >> put f
64+ put (For e b s) = putWord8 4 >> put e >> put b >> put s
65+ put (Partial e) = putWord8 5 >> put e
66+ put TrimL = putWord8 6
67+ put TrimR = putWord8 7
68+
69+ get = getWord8 >>= \tag -> case tag of
70+ 0 -> Chunk <$> get
71+ 1 -> Expr <$> get
72+ 2 -> pure Escaped
73+ 3 -> If <$> get <*> get <*> get
74+ 4 -> For <$> get <*> get <*> get
75+ 5 -> Partial <$> get
76+ 6 -> pure TrimL
77+ 7 -> pure TrimR
78+ _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
79+
80+
81+--------------------------------------------------------------------------------
82+-- | Expression in a template
83+data TemplateExpr
84+ = Ident TemplateKey
85+ | Call TemplateKey [TemplateExpr]
86+ | StringLiteral String
87+ deriving (Eq, Typeable)
88+
89+
90+--------------------------------------------------------------------------------
91+instance Show TemplateExpr where
92+ show (Ident (TemplateKey k)) = k
93+ show (Call (TemplateKey k) as) =
94+ k ++ "(" ++ intercalate ", " (map show as) ++ ")"
95+ show (StringLiteral s) = show s
96+
97+
98+--------------------------------------------------------------------------------
99+instance Binary TemplateExpr where
100+ put (Ident k) = putWord8 0 >> put k
101+ put (Call k as) = putWord8 1 >> put k >> put as
102+ put (StringLiteral s) = putWord8 2 >> put s
103+
104+ get = getWord8 >>= \tag -> case tag of
105+ 0 -> Ident <$> get
106+ 1 -> Call <$> get <*> get
107+ 2 -> StringLiteral <$> get
108+ _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
109+
110+
111+--------------------------------------------------------------------------------
112+readTemplateElems :: String -> [TemplateElement]
113+readTemplateElems = readTemplateElemsFile "{literal}"
114+
115+
116+--------------------------------------------------------------------------------
117+readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
118+readTemplateElemsFile file input = case P.parse templateElems file input of
119+ Left err -> error $ "Cannot parse template: " ++ show err
120+ Right t -> t
121+
122+
123+--------------------------------------------------------------------------------
124+templateElems :: P.Parser [TemplateElement]
125+templateElems = mconcat <$> P.many (P.choice [ lift chunk
126+ , lift escaped
127+ , conditional
128+ , for
129+ , partial
130+ , expr
131+ ])
132+ where lift = fmap (:[])
133+
134+
135+--------------------------------------------------------------------------------
136+chunk :: P.Parser TemplateElement
137+chunk = Chunk <$> P.many1 (P.noneOf "$")
138+
139+
140+--------------------------------------------------------------------------------
141+expr :: P.Parser [TemplateElement]
142+expr = P.try $ do
143+ trimLExpr <- trimOpen
144+ e <- expr'
145+ trimRExpr <- trimClose
146+ return $ [TrimL | trimLExpr] ++ [Expr e] ++ [TrimR | trimRExpr]
147+
148+
149+--------------------------------------------------------------------------------
150+expr' :: P.Parser TemplateExpr
151+expr' = stringLiteral <|> call <|> ident
152+
153+
154+--------------------------------------------------------------------------------
155+escaped :: P.Parser TemplateElement
156+escaped = Escaped <$ P.try (P.string "$$")
157+
158+
159+--------------------------------------------------------------------------------
160+trimOpen :: P.Parser Bool
161+trimOpen = do
162+ void $ P.char '$'
163+ trimLIf <- P.optionMaybe $ P.try (P.char '-')
164+ pure $ isJust trimLIf
165+
166+
167+--------------------------------------------------------------------------------
168+trimClose :: P.Parser Bool
169+trimClose = do
170+ trimIfR <- P.optionMaybe $ P.try (P.char '-')
171+ void $ P.char '$'
172+ pure $ isJust trimIfR
173+
174+
175+--------------------------------------------------------------------------------
176+conditional :: P.Parser [TemplateElement]
177+conditional = P.try $ do
178+ -- if
179+ trimLIf <- trimOpen
180+ void $ P.string "if("
181+ e <- expr'
182+ void $ P.char ')'
183+ trimRIf <- trimClose
184+ -- then
185+ thenBranch <- templateElems
186+ -- else
187+ elseParse <- opt "else"
188+ -- endif
189+ trimLEnd <- trimOpen
190+ void $ P.string "endif"
191+ trimREnd <- trimClose
192+
193+ -- As else is optional we need to sort out where any Trim_s need to go.
194+ let (thenBody, elseBody) = maybe (thenNoElse, Nothing) thenElse elseParse
195+ where thenNoElse =
196+ [TrimR | trimRIf] ++ thenBranch ++ [TrimL | trimLEnd]
197+
198+ thenElse (trimLElse, elseBranch, trimRElse) = (thenB, elseB)
199+ where thenB = [TrimR | trimRIf]
200+ ++ thenBranch
201+ ++ [TrimL | trimLElse]
202+
203+ elseB = Just $ [TrimR | trimRElse]
204+ ++ elseBranch
205+ ++ [TrimL | trimLEnd]
206+
207+ pure $ [TrimL | trimLIf] ++ [If e thenBody elseBody] ++ [TrimR | trimREnd]
208+
209+
210+--------------------------------------------------------------------------------
211+for :: P.Parser [TemplateElement]
212+for = P.try $ do
213+ -- for
214+ trimLFor <- trimOpen
215+ void $ P.string "for("
216+ e <- expr'
217+ void $ P.char ')'
218+ trimRFor <- trimClose
219+ -- body
220+ bodyBranch <- templateElems
221+ -- sep
222+ sepParse <- opt "sep"
223+ -- endfor
224+ trimLEnd <- trimOpen
225+ void $ P.string "endfor"
226+ trimREnd <- trimClose
227+
228+ -- As sep is optional we need to sort out where any Trim_s need to go.
229+ let (forBody, sepBody) = maybe (forNoSep, Nothing) forSep sepParse
230+ where forNoSep =
231+ [TrimR | trimRFor] ++ bodyBranch ++ [TrimL | trimLEnd]
232+
233+ forSep (trimLSep, sepBranch, trimRSep) = (forB, sepB)
234+ where forB = [TrimR | trimRFor]
235+ ++ bodyBranch
236+ ++ [TrimL | trimLSep]
237+
238+ sepB = Just $ [TrimR | trimRSep]
239+ ++ sepBranch
240+ ++ [TrimL | trimLEnd]
241+
242+ pure $ [TrimL | trimLFor] ++ [For e forBody sepBody] ++ [TrimR | trimREnd]
243+
244+
245+--------------------------------------------------------------------------------
246+partial :: P.Parser [TemplateElement]
247+partial = P.try $ do
248+ trimLPart <- trimOpen
249+ void $ P.string "partial("
250+ e <- expr'
251+ void $ P.char ')'
252+ trimRPart <- trimClose
253+
254+ pure $ [TrimL | trimLPart] ++ [Partial e] ++ [TrimR | trimRPart]
255+
256+
257+--------------------------------------------------------------------------------
258+ident :: P.Parser TemplateExpr
259+ident = P.try $ Ident <$> key
260+
261+
262+--------------------------------------------------------------------------------
263+call :: P.Parser TemplateExpr
264+call = P.try $ do
265+ f <- key
266+ void $ P.char '('
267+ P.spaces
268+ as <- P.sepBy expr' (P.spaces >> P.char ',' >> P.spaces)
269+ P.spaces
270+ void $ P.char ')'
271+ return $ Call f as
272+
273+
274+--------------------------------------------------------------------------------
275+stringLiteral :: P.Parser TemplateExpr
276+stringLiteral = do
277+ void $ P.char '\"'
278+ str <- P.many $ do
279+ x <- P.noneOf "\""
280+ if x == '\\' then P.anyChar else return x
281+ void $ P.char '\"'
282+ return $ StringLiteral str
283+
284+
285+--------------------------------------------------------------------------------
286+key :: P.Parser TemplateKey
287+key = TemplateKey <$> metadataKey
288+
289+
290+--------------------------------------------------------------------------------
291+opt :: String -> P.Parser (Maybe (Bool, [TemplateElement], Bool))
292+opt clause = P.optionMaybe $ P.try $ do
293+ trimL <- trimOpen
294+ void $ P.string clause
295+ trimR <- trimClose
296+ branch <- templateElems
297+ pure (trimL, branch, trimR)
298+
src/Hakyll/Web/Template/Trim.hs → src/Hakyll/Web/Template/Internal/Trim.hsrenamed+10−10
@@ -1,17 +1,17 @@
11--------------------------------------------------------------------------------
2−-- | Module for trimming whitespace
3−module Hakyll.Web.Template.Trim
2+-- | Module for trimming whitespace from tempaltes.
3+module Hakyll.Web.Template.Internal.Trim
44 ( trim
55 ) where
66
77
88--------------------------------------------------------------------------------
9−import Data.Char (isSpace)
10−import Data.List (dropWhileEnd)
9+import Data.Char (isSpace)
10+import Data.List (dropWhileEnd)
1111
1212
1313--------------------------------------------------------------------------------
14−import Hakyll.Web.Template.Internal
14+import Hakyll.Web.Template.Internal.Element
1515
1616
1717--------------------------------------------------------------------------------
@@ -58,7 +58,7 @@ redundant = recurse redundant . process
5858 -- Remove trailing 'TrimR's.
5959 process ts = foldr trailing [] ts
6060 where trailing TrimR [] = []
61− trailing x xs = x:xs
61+ trailing x xs = x:xs
6262
6363
6464--------------------------------------------------------------------------------
@@ -66,19 +66,19 @@ redundant = recurse redundant . process
6666-- [TrimL, TrimR]
6767swap :: [TemplateElement] -> [TemplateElement]
6868swap = recurse swap . process
69− where process [] = []
69+ where process [] = []
7070 process (TrimR:TrimL:ts) = TrimL:process (TrimR:ts)
71− process (t:ts) = t:process ts
71+ process (t:ts) = t:process ts
7272
7373
7474--------------------------------------------------------------------------------
7575-- | Remove 'TrimR' and 'TrimL' duplication.
7676dedupe :: [TemplateElement] -> [TemplateElement]
7777dedupe = recurse dedupe . process
78− where process [] = []
78+ where process [] = []
7979 process (TrimR:TrimR:ts) = process (TrimR:ts)
8080 process (TrimL:TrimL:ts) = process (TrimL:ts)
81− process (t:ts) = t:process ts
81+ process (t:ts) = t:process ts
8282
8383
8484--------------------------------------------------------------------------------