@@ -1,14 +1,13 @@
11--------------------------------------------------------------------------------
22-- | Module containing the template data structure
3−{-# LANGUAGE DeriveDataTypeable #-}
43{-# LANGUAGE GeneralizedNewtypeDeriving #-}
54module Hakyll.Web.Template.Internal
6− ( Template (..)
7− , TemplateKey (..)
5+ ( TemplateKey (..)
86 , TemplateExpr (..)
97 , TemplateElement (..)
10− , readTemplate
11− , readTemplateFile
8+ , templateElems
9+ , readTemplateElems
10+ , readTemplateElemsFile
1211 ) where
1312
1413
@@ -16,8 +15,9 @@ module Hakyll.Web.Template.Internal
1615import Control.Applicative ((<|>))
1716import Control.Monad (void)
1817import Data.Binary (Binary, get, getWord8, put, putWord8)
18+import Data.List (intercalate)
19+import Data.Maybe (isJust)
1920import Data.Typeable (Typeable)
20−import Data.List (intercalate)
2121import GHC.Exts (IsString (..))
2222import qualified Text.Parsec as P
2323import qualified Text.Parsec.String as P
@@ -25,25 +25,6 @@ import qualified Text.Parsec.String as P
2525
2626--------------------------------------------------------------------------------
2727import Hakyll.Core.Util.Parser
28−import Hakyll.Core.Writable
29−
30−
31−--------------------------------------------------------------------------------
32−-- | Datatype used for template substitutions.
33−newtype Template = Template
34− { unTemplate :: [TemplateElement]
35− } deriving (Show, Eq, Binary, Typeable)
36−
37−
38−--------------------------------------------------------------------------------
39−instance Writable Template where
40− -- Writing a template is impossible
41− write _ _ = return ()
42−
43−
44−--------------------------------------------------------------------------------
45−instance IsString Template where
46− fromString = readTemplate
4728
4829
4930--------------------------------------------------------------------------------
@@ -62,9 +43,14 @@ data TemplateElement
6243 = Chunk String
6344 | Expr TemplateExpr
6445 | Escaped
65− | If TemplateExpr Template (Maybe Template) -- expr, then, else
66− | For TemplateExpr Template (Maybe Template) -- expr, body, separator
67− | Partial TemplateExpr -- filename
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
6854 deriving (Show, Eq, Typeable)
6955
7056
@@ -72,10 +58,12 @@ data TemplateElement
7258instance Binary TemplateElement where
7359 put (Chunk string) = putWord8 0 >> put string
7460 put (Expr e) = putWord8 1 >> put e
75− put (Escaped) = putWord8 2
76− put (If e t f ) = putWord8 3 >> put e >> put t >> put f
61+ put Escaped = putWord8 2
62+ put (If e t f) = putWord8 3 >> put e >> put t >> put f
7763 put (For e b s) = putWord8 4 >> put e >> put b >> put s
7864 put (Partial e) = putWord8 5 >> put e
65+ put TrimL = putWord8 6
66+ put TrimR = putWord8 7
7967
8068 get = getWord8 >>= \tag -> case tag of
8169 0 -> Chunk <$> get
@@ -84,8 +72,9 @@ instance Binary TemplateElement where
8472 3 -> If <$> get <*> get <*> get
8573 4 -> For <$> get <*> get <*> get
8674 5 -> Partial <$> get
87− _ -> error $
88− "Hakyll.Web.Template.Internal: Error reading cached template"
75+ 6 -> pure TrimL
76+ 7 -> pure TrimR
77+ _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
8978
9079
9180--------------------------------------------------------------------------------
@@ -115,48 +104,45 @@ instance Binary TemplateExpr where
115104 0 -> Ident <$> get
116105 1 -> Call <$> get <*> get
117106 2 -> StringLiteral <$> get
118− _ -> error $
119− "Hakyll.Web.Tamplte.Internal: Error reading cached template"
107+ _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
120108
121109
122110--------------------------------------------------------------------------------
123−readTemplate :: String -> Template
124−readTemplate = readTemplateFile "{literal}"
111+readTemplateElems :: String -> [TemplateElement]
112+readTemplateElems = readTemplateElemsFile "{literal}"
125113
126114
127115--------------------------------------------------------------------------------
128−readTemplateFile :: FilePath -> String -> Template
129−readTemplateFile file input = case P.parse topLevelTemplate file input of
116+readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
117+readTemplateElemsFile file input = case P.parse templateElems file input of
130118 Left err -> error $ "Cannot parse template: " ++ show err
131119 Right t -> t
132120
133121
134122--------------------------------------------------------------------------------
135−topLevelTemplate :: P.Parser Template
136−topLevelTemplate = Template <$>
137− P.manyTill templateElement P.eof
138−
139−--------------------------------------------------------------------------------
140−template :: P.Parser Template
141−template = Template <$> P.many templateElement
142−
143−--------------------------------------------------------------------------------
144−templateElement :: P.Parser TemplateElement
145−templateElement = chunk <|> escaped <|> conditional <|> for <|> partial <|> expr
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 (:[])
146132
147133
148134--------------------------------------------------------------------------------
149135chunk :: P.Parser TemplateElement
150−chunk = Chunk <$> (P.many1 $ P.noneOf "$")
136+chunk = Chunk <$> P.many1 (P.noneOf "$")
151137
152138
153139--------------------------------------------------------------------------------
154−expr :: P.Parser TemplateElement
140+expr :: P.Parser [TemplateElement]
155141expr = P.try $ do
156− void $ P.char '$'
142+ trimLExpr <- trimOpen
157143 e <- expr'
158− void $ P.char '$'
159− return $ Expr e
144+ trimRExpr <- trimClose
145+ return $ [TrimL | trimLExpr] ++ [Expr e] ++ [TrimR | trimRExpr]
160146
161147
162148--------------------------------------------------------------------------------
@@ -166,40 +152,105 @@ expr' = stringLiteral <|> call <|> ident
166152
167153--------------------------------------------------------------------------------
168154escaped :: P.Parser TemplateElement
169−escaped = Escaped <$ (P.try $ P.string "$$")
155+escaped = Escaped <$ P.try (P.string "$$")
170156
171157
172158--------------------------------------------------------------------------------
173−conditional :: P.Parser TemplateElement
159+trimOpen :: P.Parser Bool
160+trimOpen = do
161+ void $ P.char '$'
162+ trimLIf <- P.optionMaybe $ P.try (P.char '-')
163+ pure $ isJust trimLIf
164+
165+
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
172+
173+
174+--------------------------------------------------------------------------------
175+conditional :: P.Parser [TemplateElement]
174176conditional = P.try $ do
175− void $ P.string "$if("
177+ -- if
178+ trimLIf <- trimOpen
179+ void $ P.string "if("
176180 e <- expr'
177− void $ P.string ")$"
178− thenBranch <- template
179− elseBranch <- P.optionMaybe $ P.try (P.string "$else$") >> template
180− void $ P.string "$endif$"
181− return $ If e thenBranch elseBranch
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]
182207
183208
184209--------------------------------------------------------------------------------
185−for :: P.Parser TemplateElement
210+for :: P.Parser [TemplateElement]
186211for = P.try $ do
187− void $ P.string "$for("
212+ -- for
213+ trimLFor <- trimOpen
214+ void $ P.string "for("
188215 e <- expr'
189− void $ P.string ")$"
190− body <- template
191− sep <- P.optionMaybe $ P.try (P.string "$sep$") >> template
192− void $ P.string "$endfor$"
193− return $ For e body sep
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]
194242
195243
196244--------------------------------------------------------------------------------
197−partial :: P.Parser TemplateElement
245+partial :: P.Parser [TemplateElement]
198246partial = P.try $ do
199− void $ P.string "$partial("
247+ trimLPart <- trimOpen
248+ void $ P.string "partial("
200249 e <- expr'
201− void $ P.string ")$"
202− return $ Partial e
250+ void $ P.char ')'
251+ trimRPart <- trimClose
252+
253+ pure $ [TrimL | trimLPart] ++ [Partial e] ++ [TrimR | trimRPart]
203254
204255
205256--------------------------------------------------------------------------------
@@ -233,3 +284,14 @@ stringLiteral = do
233284--------------------------------------------------------------------------------
234285key :: P.Parser TemplateKey
235286key = TemplateKey <$> metadataKey
287+
288+
289+--------------------------------------------------------------------------------
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)
297+