on master help sign in
Better functions in templates
commit59b6f01218eb2fbd36cb9fec6a3413093171ccda authorJasper Van der Jeugt <m@jaspervdj.be> dateMon 27 Oct 2014, 12:20 (12 years ago) parent8bc18c7
10 files changed+161−65
hakyll.cabal+2−2 src/Hakyll/Core/Util/Parser.hs+1−1 src/Hakyll/Web/Feed.hs+1−1 src/Hakyll/Web/Template.hs+36−13 src/Hakyll/Web/Template/Context.hs+19−19 src/Hakyll/Web/Template/Internal.hs+97−26 tests/Hakyll/Web/Template/Context/Tests.hs+1−1 tests/Hakyll/Web/Template/Tests.hs+1−1 tests/data/template.html+2−1 tests/data/template.html.out+1
hakyll.cabal+2−2
@@ -163,7 +163,7 @@ Library
163163 pandoc-citeproc >= 0.4 && < 0.5,
164164 parsec >= 3.0 && < 3.2,
165165 process >= 1.0 && < 1.3,
166− random >= 1.0 && < 1.1,
166+ random >= 1.0 && < 1.2,
167167 regex-base >= 0.93 && < 0.94,
168168 regex-tdfa >= 1.1 && < 1.3,
169169 tagsoup >= 0.13.1 && < 0.14,
@@ -250,7 +250,7 @@ Test-suite hakyll-tests
250250 pandoc-citeproc >= 0.4 && < 0.5,
251251 parsec >= 3.0 && < 3.2,
252252 process >= 1.0 && < 1.3,
253− random >= 1.0 && < 1.1,
253+ random >= 1.0 && < 1.2,
254254 regex-base >= 0.93 && < 0.94,
255255 regex-tdfa >= 1.1 && < 1.3,
256256 tagsoup >= 0.13.1 && < 0.14,
src/Hakyll/Core/Util/Parser.hs+1−1
@@ -16,7 +16,7 @@ import Text.Parsec.String (Parser)
1616--------------------------------------------------------------------------------
1717metadataKey :: Parser String
1818metadataKey = do
19− i <- (:) <$> P.letter <*> (P.many $ P.alphaNum <|> P.oneOf " _-.")
19+ i <- (:) <$> P.letter <*> (P.many $ P.alphaNum <|> P.oneOf "_-.")
2020 if i `elem` reservedKeys then mzero else return i
2121
2222
src/Hakyll/Web/Feed.hs+1−1
@@ -96,7 +96,7 @@ renderFeed feedPath itemPath config itemContext items = do
9696 -- recent.
9797 updatedField = field "updated" $ \_ -> case items of
9898 [] -> return "Unknown"
99− (x : _) -> unContext itemContext' "updated" x >>= \cf -> case cf of
99+ (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
100100 ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
101101 StringField s -> return s
102102
src/Hakyll/Web/Template.hs+36−13
@@ -115,7 +115,7 @@
115115-- That is, calling @$partial$@ is equivalent to just copying and pasting
116116-- template code.
117117--
118−
118+{-# LANGUAGE ScopedTypeVariables #-}
119119module Hakyll.Web.Template
120120 ( Template
121121 , templateCompiler
@@ -161,44 +161,67 @@ applyTemplate tpl context item = do
161161
162162
163163--------------------------------------------------------------------------------
164−applyTemplate' :: Template -- ^ Template
165− -> Context a -- ^ Context
166− -> Item a -- ^ Page
167− -> Compiler String -- ^ Resulting item
164+applyTemplate'
165+ :: forall a.
166+ Template -- ^ Template
167+ -> Context a -- ^ Context
168+ -> Item a -- ^ Page
169+ -> Compiler String -- ^ Resulting item
168170applyTemplate' tpl context x = go tpl
169171 where
172+ context' :: String -> [String] -> Item a -> Compiler ContextField
170173 context' = unContext (context `mappend` missingField)
174+
171175 go = liftM concat . mapM applyElem . unTemplate
172176
177+ ---------------------------------------------------------------------------
178+
179+ applyElem :: TemplateElement -> Compiler String
180+
173181 applyElem (Chunk c) = return c
174182
175− applyElem Escaped = return "$"
183+ applyElem (Expr e) = applyExpr e >>= getString e
176184
177− applyElem (Key k) = context' k x >>= getString k
185+ applyElem Escaped = return "$"
178186
179− applyElem (If k t mf) = (context' k x >> go t) `catchError` handler
187+ applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler
180188 where
181189 handler _ = case mf of
182190 Nothing -> return ""
183191 Just f -> go f
184192
185− applyElem (For k b s) = context' k x >>= \cf -> case cf of
193+ applyElem (For e b s) = applyExpr e >>= \cf -> case cf of
186194 StringField _ -> fail $
187195 "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
188− "got StringField for key " ++ show k
196+ "got StringField for expr " ++ show e
189197 ListField c xs -> do
190198 sep <- maybe (return "") go s
191199 bs <- mapM (applyTemplate' b c) xs
192200 return $ intercalate sep bs
193201
194− applyElem (Partial p) = do
202+ applyElem (Partial e) = do
203+ p <- applyExpr e >>= getString e
195204 tpl' <- loadBody (fromFilePath p)
196205 applyTemplate' tpl' context x
197206
207+ ---------------------------------------------------------------------------
208+
209+ applyExpr :: TemplateExpr -> Compiler ContextField
210+
211+ applyExpr (Ident (TemplateKey k)) = context' k [] x
212+
213+ applyExpr (Call (TemplateKey k) args) = do
214+ args' <- mapM (\e -> applyExpr e >>= getString e) args
215+ context' k args' x
216+
217+ applyExpr (StringLiteral s) = return (StringField s)
218+
219+ ----------------------------------------------------------------------------
220+
198221 getString _ (StringField s) = return s
199− getString k (ListField _ _) = fail $
222+ getString e (ListField _ _) = fail $
200223 "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
201− "got ListField for key " ++ show k
224+ "got ListField for expr " ++ show e
202225
203226
204227--------------------------------------------------------------------------------
src/Hakyll/Web/Template/Context.hs+19−19
@@ -69,29 +69,30 @@ data ContextField
6969-- @
7070-- 'metadataField' \<\> field \"date\" fDate
7171-- @
72−--
72+--
7373newtype Context a = Context
74− { unContext :: String -> Item a -> Compiler ContextField
74+ { unContext :: String -> [String] -> Item a -> Compiler ContextField
7575 }
7676
7777
7878--------------------------------------------------------------------------------
7979instance Monoid (Context a) where
8080 mempty = missingField
81− mappend (Context f) (Context g) = Context $ \k i -> f k i <|> g k i
81+ mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
8282
8383
8484--------------------------------------------------------------------------------
8585field' :: String -> (Item a -> Compiler ContextField) -> Context a
86−field' key value = Context $ \k i -> if k == key then value i else empty
86+field' key value = Context $ \k _ i -> if k == key then value i else empty
8787
8888
8989--------------------------------------------------------------------------------
9090-- | Constructs a new field in the 'Context.'
91−field :: String -- ^ Key
92− -> (Item a -> Compiler String) -- ^ Function that constructs a
93− -- value based on the item
94− -> Context a
91+field
92+ :: String -- ^ Key
93+ -> (Item a -> Compiler String) -- ^ Function that constructs a value based
94+ -- on the item
95+ -> Context a
9596field key value = field' key (fmap StringField . value)
9697
9798
@@ -108,17 +109,16 @@ listField key c xs = field' key $ \_ -> fmap (ListField c) xs
108109
109110--------------------------------------------------------------------------------
110111functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
111−functionField name value = Context $ \k i -> case words k of
112− [] -> empty
113− (n : args)
114− | n == name -> StringField <$> value args i
115− | otherwise -> empty
112+functionField name value = Context $ \k args i ->
113+ if k == name
114+ then StringField <$> value args i
115+ else empty
116116
117117
118118--------------------------------------------------------------------------------
119119mapContext :: (String -> String) -> Context a -> Context a
120−mapContext f (Context c) = Context $ \k i -> do
121− fld <- c k i
120+mapContext f (Context c) = Context $ \k a i -> do
121+ fld <- c k a i
122122 case fld of
123123 StringField str -> return $ StringField (f str)
124124 ListField _ _ -> fail $
@@ -132,12 +132,12 @@ mapContext f (Context c) = Context $ \k i -> do
132132-- 1. A @$body$@ field
133133--
134134-- 2. Metadata fields
135−--
135+--
136136-- 3. A @$url$@ 'urlField'
137137--
138138-- 4. A @$path$@ 'pathField'
139139--
140−-- 5. A @$title$@ 'titleField'
140+-- 5. A @$title$@ 'titleField'
141141defaultContext :: Context String
142142defaultContext =
143143 bodyField "body" `mappend`
@@ -162,7 +162,7 @@ bodyField key = field key $ return . itemBody
162162--------------------------------------------------------------------------------
163163-- | Map any field to its metadata value, if present
164164metadataField :: Context a
165−metadataField = Context $ \k i -> do
165+metadataField = Context $ \k _ i -> do
166166 value <- getMetadataField (itemIdentifier i) k
167167 maybe empty (return . StringField) value
168168
@@ -310,6 +310,6 @@ teaserField key snapshot = field key $ \item -> do
310310
311311--------------------------------------------------------------------------------
312312missingField :: Context a
313−missingField = Context $ \k i -> fail $
313+missingField = Context $ \k _ i -> fail $
314314 "Missing field $" ++ k ++ "$ in context for item " ++
315315 show (itemIdentifier i)
src/Hakyll/Web/Template/Internal.hs+97−26
@@ -4,6 +4,8 @@
44{-# LANGUAGE GeneralizedNewtypeDeriving #-}
55module Hakyll.Web.Template.Internal
66 ( Template (..)
7+ , TemplateKey (..)
8+ , TemplateExpr (..)
79 , TemplateElement (..)
810 , readTemplate
911 ) where
@@ -14,6 +16,7 @@ import Control.Applicative (pure, (<$), (<$>), (<*>), (<|>))
1416import Control.Monad (void)
1517import Data.Binary (Binary, get, getWord8, put, putWord8)
1618import Data.Typeable (Typeable)
19+import Data.List (intercalate)
1720import GHC.Exts (IsString (..))
1821import qualified Text.Parsec as P
1922import qualified Text.Parsec.String as P
@@ -37,30 +40,45 @@ instance Writable Template where
3740 write _ _ = return ()
3841
3942
43+--------------------------------------------------------------------------------
44+instance IsString Template where
45+ fromString = readTemplate
46+
47+
48+--------------------------------------------------------------------------------
49+newtype TemplateKey = TemplateKey String
50+ deriving (Binary, Show, Eq, Typeable)
51+
52+
53+--------------------------------------------------------------------------------
54+instance IsString TemplateKey where
55+ fromString = TemplateKey
56+
57+
4058--------------------------------------------------------------------------------
4159-- | Elements of a template.
4260data TemplateElement
4361 = Chunk String
44− | Key String
62+ | Expr TemplateExpr
4563 | Escaped
46− | If String Template (Maybe Template) -- key, then branch, else branch
47− | For String Template (Maybe Template) -- key, body, separator
48− | Partial String -- filename
64+ | If TemplateExpr Template (Maybe Template) -- expr, then, else
65+ | For TemplateExpr Template (Maybe Template) -- expr, body, separator
66+ | Partial TemplateExpr -- filename
4967 deriving (Show, Eq, Typeable)
5068
5169
5270--------------------------------------------------------------------------------
5371instance Binary TemplateElement where
5472 put (Chunk string) = putWord8 0 >> put string
55− put (Key k) = putWord8 1 >> put k
73+ put (Expr e) = putWord8 1 >> put e
5674 put (Escaped) = putWord8 2
57− put (If k t f ) = putWord8 3 >> put k >> put t >> put f
58− put (For k b s) = putWord8 4 >> put k >> put b >> put s
59− put (Partial p) = putWord8 5 >> put p
75+ put (If e t f ) = putWord8 3 >> put e >> put t >> put f
76+ put (For e b s) = putWord8 4 >> put e >> put b >> put s
77+ put (Partial e) = putWord8 5 >> put e
6078
6179 get = getWord8 >>= \tag -> case tag of
6280 0 -> Chunk <$> get
63− 1 -> Key <$> get
81+ 1 -> Expr <$> get
6482 2 -> pure Escaped
6583 3 -> If <$> get <*> get <*> get
6684 4 -> For <$> get <*> get <*> get
@@ -70,8 +88,34 @@ instance Binary TemplateElement where
7088
7189
7290--------------------------------------------------------------------------------
73−instance IsString Template where
74− fromString = readTemplate
91+-- | Expression in a template
92+data TemplateExpr
93+ = Ident TemplateKey
94+ | Call TemplateKey [TemplateExpr]
95+ | StringLiteral String
96+ deriving (Eq, Typeable)
97+
98+
99+--------------------------------------------------------------------------------
100+instance Show TemplateExpr where
101+ show (Ident (TemplateKey k)) = k
102+ show (Call (TemplateKey k) as) =
103+ k ++ "(" ++ intercalate ", " (map show as) ++ ")"
104+ show (StringLiteral s) = show s
105+
106+
107+--------------------------------------------------------------------------------
108+instance Binary TemplateExpr where
109+ put (Ident k) = putWord8 0 >> put k
110+ put (Call k as) = putWord8 1 >> put k >> put as
111+ put (StringLiteral s) = putWord8 2 >> put s
112+
113+ get = getWord8 >>= \tag -> case tag of
114+ 0 -> Ident <$> get
115+ 1 -> Call <$> get <*> get
116+ 2 -> StringLiteral <$> get
117+ _ -> error $
118+ "Hakyll.Web.Tamplte.Internal: Error reading cached template"
75119
76120
77121--------------------------------------------------------------------------------
@@ -84,7 +128,7 @@ readTemplate input = case P.parse template "" input of
84128--------------------------------------------------------------------------------
85129template :: P.Parser Template
86130template = Template <$>
87− (P.many1 $ chunk <|> escaped <|> conditional <|> for <|> partial <|> key)
131+ (P.many1 $ chunk <|> escaped <|> conditional <|> for <|> partial <|> expr)
88132
89133
90134--------------------------------------------------------------------------------
@@ -92,6 +136,20 @@ chunk :: P.Parser TemplateElement
92136chunk = Chunk <$> (P.many1 $ P.noneOf "$")
93137
94138
139+--------------------------------------------------------------------------------
140+expr :: P.Parser TemplateElement
141+expr = P.try $ do
142+ void $ P.char '$'
143+ e <- expr'
144+ void $ P.char '$'
145+ return $ Expr e
146+
147+
148+--------------------------------------------------------------------------------
149+expr' :: P.Parser TemplateExpr
150+expr' = stringLiteral <|> call <|> ident
151+
152+
95153--------------------------------------------------------------------------------
96154escaped :: P.Parser TemplateElement
97155escaped = Escaped <$ (P.try $ P.string "$$")
@@ -101,50 +159,63 @@ escaped = Escaped <$ (P.try $ P.string "$$")
101159conditional :: P.Parser TemplateElement
102160conditional = P.try $ do
103161 void $ P.string "$if("
104− i <- metadataKey
162+ e <- expr'
105163 void $ P.string ")$"
106164 thenBranch <- template
107165 elseBranch <- P.optionMaybe $ P.try (P.string "$else$") >> template
108166 void $ P.string "$endif$"
109− return $ If i thenBranch elseBranch
167+ return $ If e thenBranch elseBranch
110168
111169
112170--------------------------------------------------------------------------------
113171for :: P.Parser TemplateElement
114172for = P.try $ do
115173 void $ P.string "$for("
116− i <- metadataKey
174+ e <- expr'
117175 void $ P.string ")$"
118176 body <- template
119177 sep <- P.optionMaybe $ P.try (P.string "$sep$") >> template
120178 void $ P.string "$endfor$"
121− return $ For i body sep
179+ return $ For e body sep
122180
123181
124182--------------------------------------------------------------------------------
125183partial :: P.Parser TemplateElement
126184partial = P.try $ do
127185 void $ P.string "$partial("
128− i <- stringLiteral
186+ e <- expr'
129187 void $ P.string ")$"
130− return $ Partial i
188+ return $ Partial e
131189
132190
133191--------------------------------------------------------------------------------
134−key :: P.Parser TemplateElement
135−key = P.try $ do
136− void $ P.char '$'
137− k <- metadataKey
138− void $ P.char '$'
139− return $ Key k
192+ident :: P.Parser TemplateExpr
193+ident = P.try $ Ident <$> key
140194
141195
142196--------------------------------------------------------------------------------
143−stringLiteral :: P.Parser String
197+call :: P.Parser TemplateExpr
198+call = P.try $ do
199+ f <- key
200+ void $ P.char '('
201+ P.spaces
202+ as <- P.sepBy expr' (P.spaces >> P.char ',' >> P.spaces)
203+ P.spaces
204+ void $ P.char ')'
205+ return $ Call f as
206+
207+
208+--------------------------------------------------------------------------------
209+stringLiteral :: P.Parser TemplateExpr
144210stringLiteral = do
145211 void $ P.char '\"'
146212 str <- P.many $ do
147213 x <- P.noneOf "\""
148214 if x == '\\' then P.anyChar else return x
149215 void $ P.char '\"'
150− return str
216+ return $ StringLiteral str
217+
218+
219+--------------------------------------------------------------------------------
220+key :: P.Parser TemplateKey
221+key = TemplateKey <$> metadataKey
tests/Hakyll/Web/Template/Context/Tests.hs+1−1
@@ -51,7 +51,7 @@ testContextDone :: Store -> Provider -> Identifier -> String
5151testContextDone store provider identifier key context =
5252 testCompilerDone store provider identifier $ do
5353 item <- getResourceBody
54− cf <- unContext context key item
54+ cf <- unContext context key [] item
5555 case cf of
5656 StringField str -> return str
5757 ListField _ _ -> error $
tests/Hakyll/Web/Template/Tests.hs+1−1
@@ -76,4 +76,4 @@ testApplyJoinTemplateList = do
7676 where
7777 i1 = Item "item1" "Hello"
7878 i2 = Item "item2" "World"
79− tpl = Template [Chunk "<b>", Key "body", Chunk "</b>"]
79+ tpl = Template [Chunk "<b>", Expr (Ident "body"), Chunk "</b>"]
tests/data/template.html+2−1
@@ -1,7 +1,8 @@
11<div>
22 I'm so rich I have $$3.
33
4− $rev foo$
4+ $rev("foo")$
5+ $rev(rev("foo"))$
56
67 $if(body)$
78 I have body
tests/data/template.html.out+1
@@ -2,6 +2,7 @@
22 I'm so rich I have $3.
33
44 oof
5+ foo
56
67
78 I have body