on master help sign in
Implement foreach structure
commitcf138a415b0fbfa5153deec693f1310547f359b2 authorJasper Van der Jeugt <m@jaspervdj.be> dateMon 6 May 2013, 23:32 (13 years ago) parent738fd3d
10 files changed+120−49
src/Hakyll/Core/Util/Parser.hs+1−1 src/Hakyll/Web/Feed.hs+3−1 src/Hakyll/Web/Template.hs+36−24 src/Hakyll/Web/Template/Context.hs+20−15 src/Hakyll/Web/Template/Internal.hs+5−2 src/Hakyll/Web/Template/Read.hs+14−1 tests/Hakyll/Web/Template/Context/Tests.hs+6−1 tests/Hakyll/Web/Template/Tests.hs+9−2 tests/data/template.html+12−1 tests/data/template.html.out+14−1
src/Hakyll/Core/Util/Parser.hs+1−1
@@ -22,4 +22,4 @@ metadataKey = do
2222
2323--------------------------------------------------------------------------------
2424reservedKeys :: [String]
25−reservedKeys = ["if", "else","endif"]
25+reservedKeys = ["if", "else", "endif", "for", "sep", "endfor"]
src/Hakyll/Web/Feed.hs+3−1
@@ -97,7 +97,9 @@ renderFeed feedPath itemPath config itemContext items = do
9797 -- recent.
9898 updatedField = field "updated" $ \_ -> case items of
9999 [] -> return "Unknown"
100− (x : _) -> unContext itemContext' "updated" x
100+ (x : _) -> unContext itemContext' "updated" x >>= \cf -> case cf of
101+ ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
102+ StringField s -> return s
101103
102104
103105--------------------------------------------------------------------------------
src/Hakyll/Web/Template.hs+36−24
@@ -39,13 +39,13 @@ module Hakyll.Web.Template
3939 , applyTemplate
4040 , loadAndApplyTemplate
4141 , applyAsTemplate
42− , applyTemplateWith
4342 ) where
4443
4544
4645--------------------------------------------------------------------------------
4746import Control.Monad (liftM)
48−import Control.Monad.Error (MonadError(..))
47+import Control.Monad.Error (MonadError (..))
48+import Data.List (intercalate)
4949import Data.Monoid (mappend)
5050import Prelude hiding (id)
5151
@@ -73,12 +73,43 @@ applyTemplate :: Template -- ^ Template
7373 -> Item a -- ^ Page
7474 -> Compiler (Item String) -- ^ Resulting item
7575applyTemplate tpl context item = do
76− -- Appending missingField gives better error messages
77− let context' k x = unContext (context `mappend` missingField) k x
78− body <- applyTemplateWith context' tpl item
76+ body <- applyTemplate' tpl context item
7977 return $ itemSetBody body item
8078
8179
80+--------------------------------------------------------------------------------
81+applyTemplate' :: Template -- ^ Template
82+ -> Context a -- ^ Context
83+ -> Item a -- ^ Page
84+ -> Compiler String -- ^ Resulting item
85+applyTemplate' tpl context x = go tpl
86+ where
87+ context' = unContext (context `mappend` missingField)
88+ go = liftM concat . mapM applyElem . unTemplate
89+
90+ applyElem (Chunk c) = return c
91+ applyElem Escaped = return "$"
92+ applyElem (Key k) = context' k x >>= getString k
93+ applyElem (If k t mf) = (context' k x >> go t) `catchError` handler
94+ where
95+ handler _ = case mf of
96+ Nothing -> return ""
97+ Just f -> go f
98+ applyElem (For k b s) = context' k x >>= \cf -> case cf of
99+ StringField _ -> fail $
100+ "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
101+ "got StringField for key " ++ show k
102+ ListField c xs -> do
103+ sep <- maybe (return "") go s
104+ bs <- mapM (applyTemplate' b c) xs
105+ return $ intercalate sep bs
106+
107+ getString _ (StringField s) = return s
108+ getString k (ListField _ _) = fail $
109+ "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
110+ "got ListField for key " ++ show k
111+
112+
82113--------------------------------------------------------------------------------
83114-- | The following pattern is so common:
84115--
@@ -109,22 +140,3 @@ applyAsTemplate :: Context String -- ^ Context
109140applyAsTemplate context item =
110141 let tpl = readTemplate $ itemBody item
111142 in applyTemplate tpl context item
112−
113−
114−--------------------------------------------------------------------------------
115−-- | Overloaded apply template function to work in an arbitrary Monad.
116−applyTemplateWith :: MonadError e m
117− => (String -> a -> m String)
118− -> Template -> a -> m String
119−applyTemplateWith context tpl x = go tpl
120− where
121− go = liftM concat . mapM applyElem . unTemplate
122−
123− applyElem (Chunk c) = return c
124− applyElem Escaped = return "$"
125− applyElem (Key k) = context k x
126− applyElem (If k t mf) = (context k x >> go t) `catchError` handler
127− where
128− handler _ = case mf of
129− Nothing -> return ""
130− Just f -> go f
src/Hakyll/Web/Template/Context.hs+20−15
@@ -1,10 +1,11 @@
11--------------------------------------------------------------------------------
2+{-# LANGUAGE ExistentialQuantification #-}
23module Hakyll.Web.Template.Context
3− ( Context (..)
4− , mapContext
4+ ( ContextField (..)
5+ , Context (..)
56 , field
67 , constField
7− , functionField
8+ , listField
89
910 , defaultContext
1011 , teaserContext
@@ -45,9 +46,16 @@ import Hakyll.Core.Util.String (splitAll, needlePrefix)
4546import Hakyll.Web.Html
4647
4748
49+--------------------------------------------------------------------------------
50+-- | Mostly for internal usage
51+data ContextField
52+ = StringField String
53+ | forall a. ListField (Context a) [Item a]
54+
55+
4856--------------------------------------------------------------------------------
4957newtype Context a = Context
50− { unContext :: String -> Item a -> Compiler String
58+ { unContext :: String -> Item a -> Compiler ContextField
5159 }
5260
5361
@@ -58,13 +66,13 @@ instance Monoid (Context a) where
5866
5967
6068--------------------------------------------------------------------------------
61−mapContext :: (String -> String) -> Context a -> Context a
62−mapContext f (Context g) = Context $ \k i -> f <$> g k i
69+field' :: String -> (Item a -> Compiler ContextField) -> Context a
70+field' key value = Context $ \k i -> if k == key then value i else empty
6371
6472
6573--------------------------------------------------------------------------------
6674field :: String -> (Item a -> Compiler String) -> Context a
67−field key value = Context $ \k i -> if k == key then value i else empty
75+field key value = field' key (fmap StringField . value)
6876
6977
7078--------------------------------------------------------------------------------
@@ -73,12 +81,8 @@ constField key = field key . const . return
7381
7482
7583--------------------------------------------------------------------------------
76−functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
77−functionField name value = Context $ \k i -> case words k of
78− [] -> empty
79− (n : args)
80− | n == name -> value args i
81− | otherwise -> empty
84+listField :: String -> Context a -> Compiler [Item a] -> Context b
85+listField key c xs = field' key $ \_ -> fmap (ListField c) xs
8286
8387
8488--------------------------------------------------------------------------------
@@ -91,6 +95,7 @@ defaultContext =
9195 titleField "title" `mappend`
9296 missingField
9397
98+
9499--------------------------------------------------------------------------------
95100-- | A context with "teaser" key which contain a teaser of the item.
96101-- The item is loaded from the given snapshot (which should be saved
@@ -116,7 +121,7 @@ bodyField key = field key $ return . itemBody
116121metadataField :: Context String
117122metadataField = Context $ \k i -> do
118123 value <- getMetadataField (itemIdentifier i) k
119− maybe empty return value
124+ maybe empty (return . StringField) value
120125
121126
122127--------------------------------------------------------------------------------
@@ -135,7 +140,7 @@ pathField key = field key $ return . toFilePath . itemIdentifier
135140--------------------------------------------------------------------------------
136141-- | This title field takes the basename of the underlying file by default
137142titleField :: String -> Context a
138−titleField key = mapContext takeBaseName $ pathField key
143+titleField key = field key $ return . takeBaseName . toFilePath . itemIdentifier
139144
140145
141146--------------------------------------------------------------------------------
src/Hakyll/Web/Template/Internal.hs+5−2
@@ -37,7 +37,8 @@ data TemplateElement
3737 = Chunk String
3838 | Key String
3939 | Escaped
40− | If String Template (Maybe Template) -- key, then branch, else branch
40+ | If String Template (Maybe Template) -- key, then branch, else branch
41+ | For String Template (Maybe Template) -- key, body, separator
4142 deriving (Show, Eq, Typeable)
4243
4344
@@ -47,11 +48,13 @@ instance Binary TemplateElement where
4748 put (Key key) = putWord8 1 >> put key
4849 put (Escaped) = putWord8 2
4950 put (If key t f) = putWord8 3 >> put key >> put t >> put f
51+ put (For key b s) = putWord8 4 >> put key >> put b >> put s
5052
5153 get = getWord8 >>= \tag -> case tag of
5254 0 -> Chunk <$> get
5355 1 -> Key <$> get
5456 2 -> pure Escaped
55− 3 -> If <$> get <*> get <*> get
57+ 3 -> If <$> get <*> get <*> get
58+ 4 -> For <$> get <*> get <*> get
5659 _ -> error $
5760 "Hakyll.Web.Template.Internal: Error reading cached template"
src/Hakyll/Web/Template/Read.hs+14−1
@@ -26,7 +26,8 @@ readTemplate input = case parse template "" input of
2626
2727--------------------------------------------------------------------------------
2828template :: Parser Template
29−template = Template <$> (many1 $ chunk <|> escaped <|> conditional <|> key)
29+template = Template <$>
30+ (many1 $ chunk <|> escaped <|> conditional <|> for <|> key)
3031
3132
3233--------------------------------------------------------------------------------
@@ -51,6 +52,18 @@ conditional = try $ do
5152 return $ If i thenBranch elseBranch
5253
5354
55+--------------------------------------------------------------------------------
56+for :: Parser TemplateElement
57+for = try $ do
58+ void $ string "$for("
59+ i <- metadataKey
60+ void $ string ")$"
61+ body <- template
62+ sep <- optionMaybe $ try (string "$sep$") >> template
63+ void $ string "$endfor$"
64+ return $ For i body sep
65+
66+
5467--------------------------------------------------------------------------------
5568key :: Parser TemplateElement
5669key = try $ do
tests/Hakyll/Web/Template/Context/Tests.hs+6−1
@@ -51,4 +51,9 @@ testContextDone :: Store -> Provider -> Identifier -> String
5151testContextDone store provider identifier key context =
5252 testCompilerDone store provider identifier $ do
5353 item <- getResourceBody
54− unContext context key item
54+ cf <- unContext context key item
55+ case cf of
56+ StringField str -> return str
57+ ListField _ _ -> error $
58+ "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
59+ "Didn't expect ListField"
tests/Hakyll/Web/Template/Tests.hs+9−2
@@ -13,6 +13,7 @@ import Test.HUnit (Assertion, (@=?), (@?=))
1313
1414
1515--------------------------------------------------------------------------------
16+import Hakyll.Core.Compiler
1617import Hakyll.Core.Item
1718import Hakyll.Core.Provider
1819import Hakyll.Web.Pandoc
@@ -43,6 +44,8 @@ case01 = do
4344 item <- testCompilerDone store provider "example.md" $
4445 pandocCompiler >>= applyTemplate (itemBody tpl) testContext
4546
47+ writeFile "foo" (itemBody item)
48+
4649 out @=? itemBody item
4750 cleanTestEnv
4851
@@ -50,9 +53,13 @@ case01 = do
5053--------------------------------------------------------------------------------
5154testContext :: Context String
5255testContext = mconcat
53− [ functionField "echo" (\args _ -> return $ unwords args)
54− , defaultContext
56+ [ defaultContext
57+ , listField "authors" (bodyField "name") $ do
58+ n1 <- makeItem "Jan"
59+ n2 <- makeItem "Piet"
60+ return [n1, n2]
5561 ]
62+ where
5663
5764
5865--------------------------------------------------------------------------------
tests/data/template.html+12−1
@@ -1,16 +1,27 @@
11<div>
22 I'm so rich I have $$3.
3− $echo test$
3+
44 $if(body)$
55 I have body
66 $else$
77 or no
88 $endif$
9+
910 $if(unbound)$
1011 should not be printed
1112 $endif$
13+
1214 $if(body)$
1315 should be printed
1416 $endif$
17+
18+ <ul>
19+ $for(authors)$
20+ <li>$name$</li>
21+ $endfor$
22+ </ul>
23+
24+ $for(authors)$$name$$sep$, $endfor$
25+
1526 $body$
1627</div>
tests/data/template.html.out+14−1
@@ -1,12 +1,25 @@
11<div>
22 I'm so rich I have $3.
3− test
3+
44
55 I have body
66
7+
78
9+
810
911 should be printed
1012
13+
14+ <ul>
15+
16+ <li>Jan</li>
17+
18+ <li>Piet</li>
19+
20+ </ul>
21+
22+ Jan, Piet
23+
1124 <p>This is an example.</p>
1225</div>