Merge pull request #454 from samgd/whitespace-control

Whitespace trimming
This commit is contained in:
Jasper Van der Jeugt 2016-07-31 15:39:04 +02:00 committed by GitHub
commit 9e41414880
9 changed files with 431 additions and 114 deletions

View file

@ -117,6 +117,7 @@ Library
Hakyll.Web.Paginate Hakyll.Web.Paginate
Hakyll.Web.Template Hakyll.Web.Template
Hakyll.Web.Template.Internal Hakyll.Web.Template.Internal
Hakyll.Web.Template.Trim
Hakyll.Web.Template.Context Hakyll.Web.Template.Context
Hakyll.Web.Template.List Hakyll.Web.Template.List

View file

@ -8,7 +8,7 @@ module Hakyll.Core.Util.Parser
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Control.Monad (mzero) import Control.Monad (guard, mzero, void)
import qualified Text.Parsec as P import qualified Text.Parsec as P
import Text.Parsec.String (Parser) import Text.Parsec.String (Parser)
@ -16,7 +16,14 @@ import Text.Parsec.String (Parser)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
metadataKey :: Parser String metadataKey :: Parser String
metadataKey = do metadataKey = do
i <- (:) <$> P.letter <*> (P.many $ P.alphaNum <|> P.oneOf "_-.") -- Ensure trailing '-' binds to '$' if present.
let hyphon = P.try $ do
void $ P.char '-'
x <- P.lookAhead P.anyChar
guard $ x /= '$'
pure '-'
i <- (:) <$> P.letter <*> P.many (P.alphaNum <|> P.oneOf "_." <|> hyphon)
if i `elem` reservedKeys then mzero else return i if i `elem` reservedKeys then mzero else return i

View file

@ -23,17 +23,12 @@ module Hakyll.Web.Feed
) where ) where
--------------------------------------------------------------------------------
import Control.Monad ((<=<))
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Hakyll.Core.Compiler import Hakyll.Core.Compiler
import Hakyll.Core.Compiler.Internal import Hakyll.Core.Compiler.Internal
import Hakyll.Core.Item import Hakyll.Core.Item
import Hakyll.Web.Template import Hakyll.Web.Template
import Hakyll.Web.Template.Context import Hakyll.Web.Template.Context
import Hakyll.Web.Template.Internal
import Hakyll.Web.Template.List import Hakyll.Web.Template.List
@ -66,17 +61,16 @@ renderFeed :: FilePath -- ^ Feed template
-> [Item String] -- ^ Input items -> [Item String] -- ^ Input items
-> Compiler (Item String) -- ^ Resulting item -> Compiler (Item String) -- ^ Resulting item
renderFeed feedPath itemPath config itemContext items = do renderFeed feedPath itemPath config itemContext items = do
feedTpl <- compilerUnsafeIO $ loadTemplate feedPath feedTpl <- loadTemplate feedPath
itemTpl <- compilerUnsafeIO $ loadTemplate itemPath itemTpl <- loadTemplate itemPath
body <- makeItem =<< applyTemplateList itemTpl itemContext' items body <- makeItem =<< applyTemplateList itemTpl itemContext' items
applyTemplate feedTpl feedContext body applyTemplate feedTpl feedContext body
where where
-- Auxiliary: load a template from a datafile -- Auxiliary: load a template from a datafile
loadTemplate path = do loadTemplate path = do
file <- getDataFileName path file <- compilerUnsafeIO $ getDataFileName path
templ <- readFile file unsafeReadTemplateFile file
return $ readTemplateFile file templ
itemContext' = mconcat itemContext' = mconcat
[ itemContext [ itemContext

View file

@ -115,7 +115,31 @@
-- That is, calling @$partial$@ is equivalent to just copying and pasting -- That is, calling @$partial$@ is equivalent to just copying and pasting
-- template code. -- template code.
-- --
{-# LANGUAGE ScopedTypeVariables #-} -- In the examples above you can see that the outputs contain a lot of leftover
-- whitespace that you may wish to remove. Using @'$-'@ or @'-$'@ instead of
-- @'$'@ in a macro strips all whitespace to the left or right of that clause
-- respectively. Given the context
--
-- > listField "counts" (field "count" (return . itemBody))
-- > (sequence [makeItem "3", makeItem "2", makeItem "1"])
--
-- and a template
--
-- > <p>
-- > $for(counts)-$
-- > $count$
-- > $-sep$...
-- > $-endfor$
-- > </p>
--
-- the resulting page would look like
--
-- > <p>
-- > 3...2...1
-- > </p>
--
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Hakyll.Web.Template module Hakyll.Web.Template
( Template ( Template
, templateBodyCompiler , templateBodyCompiler
@ -124,13 +148,16 @@ module Hakyll.Web.Template
, loadAndApplyTemplate , loadAndApplyTemplate
, applyAsTemplate , applyAsTemplate
, readTemplate , readTemplate
, unsafeReadTemplateFile
) where ) where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Monad (liftM)
import Control.Monad.Except (MonadError (..)) import Control.Monad.Except (MonadError (..))
import Data.Binary (Binary)
import Data.List (intercalate) import Data.List (intercalate)
import Data.Typeable (Typeable)
import GHC.Exts (IsString (..))
import Prelude hiding (id) import Prelude hiding (id)
@ -138,17 +165,47 @@ import Prelude hiding (id)
import Hakyll.Core.Compiler import Hakyll.Core.Compiler
import Hakyll.Core.Identifier import Hakyll.Core.Identifier
import Hakyll.Core.Item import Hakyll.Core.Item
import Hakyll.Core.Writable
import Hakyll.Web.Template.Context import Hakyll.Web.Template.Context
import Hakyll.Web.Template.Internal import Hakyll.Web.Template.Internal
import Hakyll.Web.Template.Trim
--------------------------------------------------------------------------------
-- | Datatype used for template substitutions.
newtype Template = Template
{ unTemplate :: [TemplateElement]
} deriving (Show, Eq, 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 :: [TemplateElement] -> Template
template = Template . trim
--------------------------------------------------------------------------------
readTemplate :: String -> Template
readTemplate = Template . trim . readTemplateElems
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Read a template, without metadata header -- | Read a template, without metadata header
templateBodyCompiler :: Compiler (Item Template) templateBodyCompiler :: Compiler (Item Template)
templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
item <- getResourceBody item <- getResourceBody
file <- getResourceFilePath file <- getResourceFilePath
return $ fmap (readTemplateFile file) item return $ fmap (template . readTemplateElemsFile file) item
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Read complete file contents as a template -- | Read complete file contents as a template
@ -156,7 +213,7 @@ templateCompiler :: Compiler (Item Template)
templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
item <- getResourceString item <- getResourceString
file <- getResourceFilePath file <- getResourceFilePath
return $ fmap (readTemplateFile file) item return $ fmap (template . readTemplateElemsFile file) item
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -165,28 +222,35 @@ applyTemplate :: Template -- ^ Template
-> Item a -- ^ Page -> Item a -- ^ Page
-> Compiler (Item String) -- ^ Resulting item -> Compiler (Item String) -- ^ Resulting item
applyTemplate tpl context item = do applyTemplate tpl context item = do
body <- applyTemplate' tpl context item body <- applyTemplate' (unTemplate tpl) context item
return $ itemSetBody body item return $ itemSetBody body item
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
applyTemplate' applyTemplate'
:: forall a. :: forall a.
Template -- ^ Template [TemplateElement] -- ^ Unwrapped Template
-> Context a -- ^ Context -> Context a -- ^ Context
-> Item a -- ^ Page -> Item a -- ^ Page
-> Compiler String -- ^ Resulting item -> Compiler String -- ^ Resulting item
applyTemplate' tpl context x = go tpl applyTemplate' tes context x = go tes
where where
context' :: String -> [String] -> Item a -> Compiler ContextField context' :: String -> [String] -> Item a -> Compiler ContextField
context' = unContext (context `mappend` missingField) context' = unContext (context `mappend` missingField)
go = liftM concat . mapM applyElem . unTemplate go = fmap concat . mapM applyElem
trimError = error $ "Hakyll.Web.Template.applyTemplate: template not " ++
"fully trimmed."
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
applyElem :: TemplateElement -> Compiler String applyElem :: TemplateElement -> Compiler String
applyElem TrimL = trimError
applyElem TrimR = trimError
applyElem (Chunk c) = return c applyElem (Chunk c) = return c
applyElem (Expr e) = applyExpr e >>= getString e applyElem (Expr e) = applyExpr e >>= getString e
@ -261,6 +325,14 @@ applyAsTemplate :: Context String -- ^ Context
-> Item String -- ^ Item and template -> Item String -- ^ Item and template
-> Compiler (Item String) -- ^ Resulting item -> Compiler (Item String) -- ^ Resulting item
applyAsTemplate context item = applyAsTemplate context item =
let tpl = readTemplateFile file (itemBody item) let tpl = template $ readTemplateElemsFile file (itemBody item)
file = toFilePath $ itemIdentifier item file = toFilePath $ itemIdentifier item
in applyTemplate tpl context item in applyTemplate tpl context item
--------------------------------------------------------------------------------
unsafeReadTemplateFile :: FilePath -> Compiler Template
unsafeReadTemplateFile file = do
tpl <- unsafeCompiler $ readFile file
pure $ template $ readTemplateElemsFile file tpl

View file

@ -1,14 +1,13 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Module containing the template data structure -- | Module containing the template data structure
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Hakyll.Web.Template.Internal module Hakyll.Web.Template.Internal
( Template (..) ( TemplateKey (..)
, TemplateKey (..)
, TemplateExpr (..) , TemplateExpr (..)
, TemplateElement (..) , TemplateElement (..)
, readTemplate , templateElems
, readTemplateFile , readTemplateElems
, readTemplateElemsFile
) where ) where
@ -16,8 +15,9 @@ module Hakyll.Web.Template.Internal
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Control.Monad (void) import Control.Monad (void)
import Data.Binary (Binary, get, getWord8, put, putWord8) import Data.Binary (Binary, get, getWord8, put, putWord8)
import Data.List (intercalate)
import Data.Maybe (isJust)
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Data.List (intercalate)
import GHC.Exts (IsString (..)) import GHC.Exts (IsString (..))
import qualified Text.Parsec as P import qualified Text.Parsec as P
import qualified Text.Parsec.String as P import qualified Text.Parsec.String as P
@ -25,25 +25,6 @@ import qualified Text.Parsec.String as P
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Hakyll.Core.Util.Parser import Hakyll.Core.Util.Parser
import Hakyll.Core.Writable
--------------------------------------------------------------------------------
-- | Datatype used for template substitutions.
newtype Template = Template
{ unTemplate :: [TemplateElement]
} deriving (Show, Eq, Binary, Typeable)
--------------------------------------------------------------------------------
instance Writable Template where
-- Writing a template is impossible
write _ _ = return ()
--------------------------------------------------------------------------------
instance IsString Template where
fromString = readTemplate
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -62,9 +43,14 @@ data TemplateElement
= Chunk String = Chunk String
| Expr TemplateExpr | Expr TemplateExpr
| Escaped | Escaped
| If TemplateExpr Template (Maybe Template) -- expr, then, else -- expr, then, else
| For TemplateExpr Template (Maybe Template) -- expr, body, separator | If TemplateExpr [TemplateElement] (Maybe [TemplateElement])
| Partial TemplateExpr -- filename -- expr, body, separator
| For TemplateExpr [TemplateElement] (Maybe [TemplateElement])
-- filename
| Partial TemplateExpr
| TrimL
| TrimR
deriving (Show, Eq, Typeable) deriving (Show, Eq, Typeable)
@ -72,10 +58,12 @@ data TemplateElement
instance Binary TemplateElement where instance Binary TemplateElement where
put (Chunk string) = putWord8 0 >> put string put (Chunk string) = putWord8 0 >> put string
put (Expr e) = putWord8 1 >> put e put (Expr e) = putWord8 1 >> put e
put (Escaped) = putWord8 2 put Escaped = putWord8 2
put (If e t f ) = putWord8 3 >> put e >> put t >> put f put (If e t f) = putWord8 3 >> put e >> put t >> put f
put (For e b s) = putWord8 4 >> put e >> put b >> put s put (For e b s) = putWord8 4 >> put e >> put b >> put s
put (Partial e) = putWord8 5 >> put e put (Partial e) = putWord8 5 >> put e
put TrimL = putWord8 6
put TrimR = putWord8 7
get = getWord8 >>= \tag -> case tag of get = getWord8 >>= \tag -> case tag of
0 -> Chunk <$> get 0 -> Chunk <$> get
@ -84,8 +72,9 @@ instance Binary TemplateElement where
3 -> If <$> get <*> get <*> get 3 -> If <$> get <*> get <*> get
4 -> For <$> get <*> get <*> get 4 -> For <$> get <*> get <*> get
5 -> Partial <$> get 5 -> Partial <$> get
_ -> error $ 6 -> pure TrimL
"Hakyll.Web.Template.Internal: Error reading cached template" 7 -> pure TrimR
_ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -115,48 +104,45 @@ instance Binary TemplateExpr where
0 -> Ident <$> get 0 -> Ident <$> get
1 -> Call <$> get <*> get 1 -> Call <$> get <*> get
2 -> StringLiteral <$> get 2 -> StringLiteral <$> get
_ -> error $ _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
"Hakyll.Web.Tamplte.Internal: Error reading cached template"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
readTemplate :: String -> Template readTemplateElems :: String -> [TemplateElement]
readTemplate = readTemplateFile "{literal}" readTemplateElems = readTemplateElemsFile "{literal}"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
readTemplateFile :: FilePath -> String -> Template readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
readTemplateFile file input = case P.parse topLevelTemplate file input of readTemplateElemsFile file input = case P.parse templateElems file input of
Left err -> error $ "Cannot parse template: " ++ show err Left err -> error $ "Cannot parse template: " ++ show err
Right t -> t Right t -> t
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
topLevelTemplate :: P.Parser Template templateElems :: P.Parser [TemplateElement]
topLevelTemplate = Template <$> templateElems = mconcat <$> P.many (P.choice [ lift chunk
P.manyTill templateElement P.eof , lift escaped
, conditional
-------------------------------------------------------------------------------- , for
template :: P.Parser Template , partial
template = Template <$> P.many templateElement , expr
])
-------------------------------------------------------------------------------- where lift = fmap (:[])
templateElement :: P.Parser TemplateElement
templateElement = chunk <|> escaped <|> conditional <|> for <|> partial <|> expr
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
chunk :: P.Parser TemplateElement chunk :: P.Parser TemplateElement
chunk = Chunk <$> (P.many1 $ P.noneOf "$") chunk = Chunk <$> P.many1 (P.noneOf "$")
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
expr :: P.Parser TemplateElement expr :: P.Parser [TemplateElement]
expr = P.try $ do expr = P.try $ do
void $ P.char '$' trimLExpr <- trimOpen
e <- expr' e <- expr'
void $ P.char '$' trimRExpr <- trimClose
return $ Expr e return $ [TrimL | trimLExpr] ++ [Expr e] ++ [TrimR | trimRExpr]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -166,40 +152,105 @@ expr' = stringLiteral <|> call <|> ident
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
escaped :: P.Parser TemplateElement escaped :: P.Parser TemplateElement
escaped = Escaped <$ (P.try $ P.string "$$") escaped = Escaped <$ P.try (P.string "$$")
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
conditional :: P.Parser TemplateElement trimOpen :: P.Parser Bool
trimOpen = do
void $ P.char '$'
trimLIf <- P.optionMaybe $ P.try (P.char '-')
pure $ isJust trimLIf
--------------------------------------------------------------------------------
trimClose :: P.Parser Bool
trimClose = do
trimIfR <- P.optionMaybe $ P.try (P.char '-')
void $ P.char '$'
pure $ isJust trimIfR
--------------------------------------------------------------------------------
conditional :: P.Parser [TemplateElement]
conditional = P.try $ do conditional = P.try $ do
void $ P.string "$if(" -- if
trimLIf <- trimOpen
void $ P.string "if("
e <- expr' e <- expr'
void $ P.string ")$" void $ P.char ')'
thenBranch <- template trimRIf <- trimClose
elseBranch <- P.optionMaybe $ P.try (P.string "$else$") >> template -- then
void $ P.string "$endif$" thenBranch <- templateElems
return $ If e thenBranch elseBranch -- else
elseParse <- opt "else"
-- endif
trimLEnd <- trimOpen
void $ P.string "endif"
trimREnd <- trimClose
-- As else is optional we need to sort out where any Trim_s need to go.
let (thenBody, elseBody) = maybe (thenNoElse, Nothing) thenElse elseParse
where thenNoElse =
[TrimR | trimRIf] ++ thenBranch ++ [TrimL | trimLEnd]
thenElse (trimLElse, elseBranch, trimRElse) = (thenB, elseB)
where thenB = [TrimR | trimRIf]
++ thenBranch
++ [TrimL | trimLElse]
elseB = Just $ [TrimR | trimRElse]
++ elseBranch
++ [TrimL | trimLEnd]
pure $ [TrimL | trimLIf] ++ [If e thenBody elseBody] ++ [TrimR | trimREnd]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
for :: P.Parser TemplateElement for :: P.Parser [TemplateElement]
for = P.try $ do for = P.try $ do
void $ P.string "$for(" -- for
trimLFor <- trimOpen
void $ P.string "for("
e <- expr' e <- expr'
void $ P.string ")$" void $ P.char ')'
body <- template trimRFor <- trimClose
sep <- P.optionMaybe $ P.try (P.string "$sep$") >> template -- body
void $ P.string "$endfor$" bodyBranch <- templateElems
return $ For e body sep -- sep
sepParse <- opt "sep"
-- endfor
trimLEnd <- trimOpen
void $ P.string "endfor"
trimREnd <- trimClose
-- As sep is optional we need to sort out where any Trim_s need to go.
let (forBody, sepBody) = maybe (forNoSep, Nothing) forSep sepParse
where forNoSep =
[TrimR | trimRFor] ++ bodyBranch ++ [TrimL | trimLEnd]
forSep (trimLSep, sepBranch, trimRSep) = (forB, sepB)
where forB = [TrimR | trimRFor]
++ bodyBranch
++ [TrimL | trimLSep]
sepB = Just $ [TrimR | trimRSep]
++ sepBranch
++ [TrimL | trimLEnd]
pure $ [TrimL | trimLFor] ++ [For e forBody sepBody] ++ [TrimR | trimREnd]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
partial :: P.Parser TemplateElement partial :: P.Parser [TemplateElement]
partial = P.try $ do partial = P.try $ do
void $ P.string "$partial(" trimLPart <- trimOpen
void $ P.string "partial("
e <- expr' e <- expr'
void $ P.string ")$" void $ P.char ')'
return $ Partial e trimRPart <- trimClose
pure $ [TrimL | trimLPart] ++ [Partial e] ++ [TrimR | trimRPart]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -233,3 +284,14 @@ stringLiteral = do
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
key :: P.Parser TemplateKey key :: P.Parser TemplateKey
key = TemplateKey <$> metadataKey key = TemplateKey <$> metadataKey
--------------------------------------------------------------------------------
opt :: String -> P.Parser (Maybe (Bool, [TemplateElement], Bool))
opt clause = P.optionMaybe $ P.try $ do
trimL <- trimOpen
void $ P.string clause
trimR <- trimClose
branch <- templateElems
pure (trimL, branch, trimR)

View file

@ -0,0 +1,95 @@
--------------------------------------------------------------------------------
-- | Module for trimming whitespace
module Hakyll.Web.Template.Trim
( trim
) where
--------------------------------------------------------------------------------
import Data.Char (isSpace)
import Data.List (dropWhileEnd)
--------------------------------------------------------------------------------
import Hakyll.Web.Template.Internal
--------------------------------------------------------------------------------
trim :: [TemplateElement] -> [TemplateElement]
trim = cleanse . canonicalize
--------------------------------------------------------------------------------
-- | Apply the Trim nodes to the Chunks.
cleanse :: [TemplateElement] -> [TemplateElement]
cleanse = recurse cleanse . process
where process [] = []
process (TrimR:Chunk str:ts) = let str' = dropWhile isSpace str
in if null str'
then process ts
-- Might need to TrimL.
else process $ Chunk str':ts
process (Chunk str:TrimL:ts) = let str' = dropWhileEnd isSpace str
in if null str'
then process ts
else Chunk str':process ts
process (t:ts) = t:process ts
--------------------------------------------------------------------------------
-- | Enforce the invariant that:
--
-- * Every 'TrimL' has a 'Chunk' to its left.
-- * Every 'TrimR' has a 'Chunk' to its right.
--
canonicalize :: [TemplateElement] -> [TemplateElement]
canonicalize = go
where go t = let t' = redundant . swap $ dedupe t
in if t == t' then t else go t'
--------------------------------------------------------------------------------
-- | Remove the 'TrimR' and 'TrimL's that are no-ops.
redundant :: [TemplateElement] -> [TemplateElement]
redundant = recurse redundant . process
where -- Remove the leading 'TrimL's.
process (TrimL:ts) = process ts
-- Remove trailing 'TrimR's.
process ts = foldr trailing [] ts
where trailing TrimR [] = []
trailing x xs = x:xs
--------------------------------------------------------------------------------
-- >>> swap $ [TrimR, TrimL]
-- [TrimL, TrimR]
swap :: [TemplateElement] -> [TemplateElement]
swap = recurse swap . process
where process [] = []
process (TrimR:TrimL:ts) = TrimL:process (TrimR:ts)
process (t:ts) = t:process ts
--------------------------------------------------------------------------------
-- | Remove 'TrimR' and 'TrimL' duplication.
dedupe :: [TemplateElement] -> [TemplateElement]
dedupe = recurse dedupe . process
where process [] = []
process (TrimR:TrimR:ts) = process (TrimR:ts)
process (TrimL:TrimL:ts) = process (TrimL:ts)
process (t:ts) = t:process ts
--------------------------------------------------------------------------------
-- | @'recurse' f t@ applies f to every '[TemplateElement]' in t.
recurse :: ([TemplateElement] -> [TemplateElement])
-> [TemplateElement]
-> [TemplateElement]
recurse _ [] = []
recurse f (x:xs) = process x:recurse f xs
where process y = case y of
If e tb eb -> If e (f tb) (f <$> eb)
For e t s -> For e (f t) (f <$> s)
_ -> y

View file

@ -6,7 +6,6 @@ module Hakyll.Web.Template.Tests
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Data.Monoid (mconcat)
import Test.Framework (Test, testGroup) import Test.Framework (Test, testGroup)
import Test.Framework.Providers.HUnit (testCase) import Test.Framework.Providers.HUnit (testCase)
import Test.HUnit (Assertion, (@=?), (@?=)) import Test.HUnit (Assertion, (@=?), (@?=))
@ -14,6 +13,7 @@ import Test.HUnit (Assertion, (@=?), (@?=))
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Hakyll.Core.Compiler import Hakyll.Core.Compiler
import Hakyll.Core.Identifier
import Hakyll.Core.Item import Hakyll.Core.Item
import Hakyll.Core.Provider import Hakyll.Core.Provider
import Hakyll.Web.Pandoc import Hakyll.Web.Pandoc
@ -27,32 +27,67 @@ import TestSuite.Util
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
tests :: Test tests :: Test
tests = testGroup "Hakyll.Core.Template.Tests" $ concat tests = testGroup "Hakyll.Core.Template.Tests" $ concat
[ [ testCase "case01" case01 [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
, testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
, testCase "applyJoinTemplateList" testApplyJoinTemplateList , testCase "applyJoinTemplateList" testApplyJoinTemplateList
] ]
, fromAssertions "readTemplate" , fromAssertions "readTemplate"
[ Template [Chunk "Hello ", Expr (Call "guest" [])] [ [Chunk "Hello ", Expr (Call "guest" [])]
@=? readTemplate "Hello $guest()$" @=? readTemplateElems "Hello $guest()$"
, Template , [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
[If (Call "a" [StringLiteral "bar"]) @=? readTemplateElems "$if(a(\"bar\"))$foo$endif$"
(Template [Chunk "foo"]) -- 'If' trim check.
Nothing] , [ TrimL
@=? readTemplate "$if(a(\"bar\"))$foo$endif$" , If (Ident (TemplateKey "body"))
[ TrimR
, Chunk "\n"
, Expr (Ident (TemplateKey "body"))
, Chunk "\n"
, TrimL
]
(Just [ TrimR
, Chunk "\n"
, Expr (Ident (TemplateKey "body"))
, Chunk "\n"
, TrimL
])
, TrimR
]
@=? readTemplateElems "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
-- 'For' trim check.
, [ TrimL
, For (Ident (TemplateKey "authors"))
[TrimR, Chunk "\n body \n", TrimL]
Nothing
, TrimR
]
@=? readTemplateElems "$-for(authors)-$\n body \n$-endfor-$"
-- 'Partial' trim check.
, [ TrimL
, Partial (StringLiteral "path")
, TrimR
]
@=? readTemplateElems "$-partial(\"path\")-$"
-- 'Expr' trim check.
, [ TrimL
, Expr (Ident (TemplateKey "foo"))
, TrimR
]
@=? readTemplateElems "$-foo-$"
] ]
] ]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
case01 :: Assertion test :: (Identifier, Identifier, Identifier) -> Assertion
case01 = do test (outf, tplf, itemf) = do
store <- newTestStore store <- newTestStore
provider <- newTestProvider store provider <- newTestProvider store
out <- resourceString provider "template.html.out" out <- resourceString provider outf
tpl <- testCompilerDone store provider "template.html" $ tpl <- testCompilerDone store provider tplf templateBodyCompiler
templateBodyCompiler item <- testCompilerDone store provider itemf $
item <- testCompilerDone store provider "example.md" $
pandocCompiler >>= applyTemplate (itemBody tpl) testContext pandocCompiler >>= applyTemplate (itemBody tpl) testContext
out @=? itemBody item out @=? itemBody item
@ -69,7 +104,6 @@ testContext = mconcat
return [n1, n2] return [n1, n2]
, functionField "rev" $ \args _ -> return $ unwords $ map reverse args , functionField "rev" $ \args _ -> return $ unwords $ map reverse args
] ]
where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -85,4 +119,4 @@ testApplyJoinTemplateList = do
where where
i1 = Item "item1" "Hello" i1 = Item "item1" "Hello"
i2 = Item "item2" "World" i2 = Item "item2" "World"
tpl = Template [Chunk "<b>", Expr (Ident "body"), Chunk "</b>"] tpl = readTemplate "<b>$body$</b>"

34
tests/data/strip.html Normal file
View file

@ -0,0 +1,34 @@
<div>
I'm so rich I have $$3.
$rev("foo")$
$-rev(rev("foo"))$
$if(body)-$
I have body
$else-$
or no
$-endif-$
$if(unbound)$
should not be printed
$endif$
$-if(body)-$
should be printed
$-endif$
<ul>
$for(authors)-$
<li>$name$</li>
$endfor-$
</ul>
$for(authors)-$
$name-$
$sep$,
$-endfor$
$body$
</div>

18
tests/data/strip.html.out Normal file
View file

@ -0,0 +1,18 @@
<div>
I'm so rich I have $3.
ooffoo
I have body
should be printed
<ul>
<li>Jan</li>
<li>Piet</li>
</ul>
Jan,Piet
<p>This is an example.</p>
</div>