StrappedTemplates (empty) → 0.1.0.0
raw patch · 9 files changed
+649/−0 lines, 9 filesdep +StrappedTemplatesdep +basedep +blaze-buildersetup-changed
Dependencies added: StrappedTemplates, base, blaze-builder, bytestring, containers, filemanip, filepath, hspec, mtl, parsec, text, transformers
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- StrappedTemplates.cabal +72/−0
- src/Text/Strapped.hs +24/−0
- src/Text/Strapped/Parser.hs +196/−0
- src/Text/Strapped/Render.hs +132/−0
- src/Text/Strapped/Types.hs +83/−0
- src/Text/Strapped/Utils.hs +33/−0
- tests/StrappedTest.hs +77/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Kyle Hanson++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Kyle Hanson nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ StrappedTemplates.cabal view
@@ -0,0 +1,72 @@+name: StrappedTemplates+version: 0.1.0.0+synopsis: General purpose templates in haskell+homepage: https://github.com/hansonkd/StrappedTemplates+license: BSD3+license-file: LICENSE+author: Kyle Hanson+maintainer: me@khanson.io+category: Text+build-type: Simple+cabal-version: >=1.8+description:+ Easy templating in haskell.+ .+ > import Control.Monad.IO.Class+ > import qualified Blaze.ByteString.Builder as B+ > import qualified Data.Text.Lazy as T+ > import Data.Time+ > + > import Text.Strapped+ > + > makeBucket :: Integer -> InputBucket IO+ > makeBucket i = bucketFromList + > [ ("is", List $ map (LitVal . LitInteger) [1..i])+ > , ("ioTime", Func (\_ -> (liftIO $ getCurrentTime) >>= (\c -> return $ LitText $ T.pack $ show c)))+ > ]+ > + > main :: IO ()+ > main = do+ > tmpls <- templateStoreFromDirectory "benchmarks/strapped_templates" ".strp"+ > case tmpls of+ > Left err -> print err+ > Right store -> do+ > rendered <- render (putStore store defaultConfig) (makeBucket 2) "base_simple.strp"+ > either (print) (print . B.toByteString) rendered+ .+ @+  ${ ioTime }+  +  {$ for i in is $}+   ${ i }+  {$ endfor $}+ @++library+ exposed-modules: Text.Strapped, Text.Strapped.Parser, Text.Strapped.Render, Text.Strapped.Types, Text.Strapped.Utils+ build-depends: base >= 4.7 && < 4.8,+ containers >= 0.5 && < 0.6,+ text >= 1.0 && < 1.2, + blaze-builder >=0.3 && < 0.4,+ parsec >=3.1 && < 3.2,+ mtl >=2.1 && < 2.3,+ transformers >= 0.4 && < 0.5,+ filemanip >=0.3.6 && < 0.3.7,+ filepath >=1.3 && < 1.4+ hs-source-dirs: src++test-suite Main+ type: exitcode-stdio-1.0+ build-depends: StrappedTemplates >= 0.1 && < 0.2,+ base >= 4.7 && < 4.8, + hspec >= 1.11 && < 1.12,+ text >= 1.0 && < 1.2,+ bytestring >= 0.10 && < 0.11,+ blaze-builder >=0.3 && < 0.4++ ghc-options: -Wall -rtsopts+ hs-source-dirs: tests+ default-language: Haskell2010+ hs-source-dirs: tests+ main-is: StrappedTest.hs+
+ src/Text/Strapped.hs view
@@ -0,0 +1,24 @@+module Text.Strapped+ ( -- * Rendering+ render+ , defaultConfig+ , showToBuilder+ -- ** Controlling variables + , combineBuckets+ , varBucket+ , bucketLookup+ , bucketFromList+ , emptyBucket+ -- * Parsing+ , parseTemplate+ -- * TemplateLoading+ , templateStoreFromList+ , templateStoreFromDirectory+ , putStore+ , module Text.Strapped.Types+ ) where+ +import Text.Strapped.Types+import Text.Strapped.Render+import Text.Strapped.Parser+import Text.Strapped.Utils
+ src/Text/Strapped/Parser.hs view
@@ -0,0 +1,196 @@+module Text.Strapped.Parser+ ( parseTemplate+ ) where++import Control.Applicative ((<*>))+import Control.Monad+import Data.Monoid+import qualified Data.Text.Lazy as T+import Blaze.ByteString.Builder as B+import Blaze.ByteString.Builder.Char.Utf8 as B+import Text.Parsec+import Text.Parsec.String+import qualified Text.Parsec.Token as P+import Text.Parsec.Language (emptyDef)+import Text.Strapped.Types+++tagStart :: GenParser Char st String+tagStart = string "{$"++tagEnd :: GenParser Char st String+tagEnd = string "$}"++wordString :: GenParser Char st String+wordString = many1 $ oneOf "_" <|> alphaNum++pathString :: GenParser Char st String+pathString = many1 $ oneOf "_./" <|> alphaNum++peekChar :: Char -> GenParser Char st ()+peekChar = void . try . lookAhead . char++tryTag :: GenParser Char st a -> GenParser Char st ()+tryTag = void . try . tag++tag :: GenParser Char st a -> GenParser Char st a+tag p = between (tagStart >> spaces) (spaces >> tagEnd) p <?> "Tag"++parseFloat :: GenParser Char st Double+parseFloat = do sign <- option 1 (do s <- oneOf "+-"+ return $ if s == '-' then-1.0 else 1.0)+ x <- P.float $ P.makeTokenParser emptyDef+ return $ sign * x++parseInt :: GenParser Char st Integer+parseInt = do sign <- option 1 (do s <- oneOf "+-"+ return $ if s == '-' then-1 else 1)+ x <- P.integer $ P.makeTokenParser emptyDef+ return $ sign * x++parseContent :: GenParser Char st a -> GenParser Char st [ParsedPiece]+parseContent end = do+ decls <- many (try $ spaces >> parseDecl)+ spaces+ extends <- optionMaybe (try $ spaces >> parseInherits)+ case (extends) of+ Just (e, epos) -> do+ includes <- manyTill parseIsIgnoreSpace end+ return $ (decls) ++ [ParsedPiece (Inherits e includes) epos]+ _ -> do+ ps <- manyTill parsePiece end+ return $ decls ++ ps+ where parseIsIgnoreSpace = do {spaces; b <- parseIsBlock; spaces; return b}++parseBlock :: GenParser Char st ParsedPiece+parseBlock = do+ pos <- getPosition+ blockName <- tag (string "block" >> spaces >> wordString) <?> "Block tag"+ blockContent <- parseContent (tryTag $ string "endblock") + return $ ParsedPiece (BlockPiece blockName blockContent) pos++parseRaw :: GenParser Char st ParsedPiece+parseRaw = do+ pos <- getPosition+ tag (string "raw") <?> "Raw tag"+ c <- anyChar+ s <- manyTill anyChar (tryTag (string "endraw"))+ return $ ParsedPiece (StaticPiece (B.fromString $ c:s)) pos++parseComment :: GenParser Char st ParsedPiece+parseComment = do+ pos <- getPosition+ tag (string "comment") <?> "Comment tag"+ c <- anyChar+ s <- manyTill anyChar (tryTag (string "endcomment"))+ return $ ParsedPiece (StaticPiece mempty) pos++parseFor :: GenParser Char st ParsedPiece+parseFor = do+ pos <- getPosition+ (newVarName, exp) <- (tagStart >> spaces >> string "for" >> argParser) <?> "For tag"+ blockContent <- parseContent (tryTag $ string "endfor") + return $ ParsedPiece (ForPiece newVarName exp blockContent) pos+ where argParser = do + spaces+ v <- wordString+ spaces >> (string "in") >> spaces+ func <- parseExpression (try $ spaces >> tagEnd)+ return (v, func)++parseDecl :: GenParser Char st ParsedPiece+parseDecl = do {spaces; decl <- parserDecl; spaces; return decl} <?> "Let tag"+ where parserDecl = do+ pos <- getPosition+ tagStart >> spaces+ string "let" >> spaces+ varName <- wordString+ spaces >> string "=" >> spaces+ func <- parseExpression (try $ spaces >> tagEnd)+ return $ ParsedPiece (Decl varName func) pos+ +parseIsBlock = do+ blockName <- tag (string "isblock" >> spaces >> wordString) <?> "Isblock tag"+ blockContent <- parseContent (tryTag $ string "endisblock") + return (blockName, blockContent)++parseInclude :: GenParser Char st ParsedPiece+parseInclude = do+ pos <- getPosition+ tag (parserInclude pos) <?> "Include tag"+ where parserInclude pos = do+ string "include" >> spaces+ includeName <- pathString+ return $ ParsedPiece (Include includeName) pos++parseInherits = do {pos <- getPosition; mtag <- tag (string "inherits" >> spaces >> pathString); return (mtag, pos)} <?> "Inherits tag"++parseFunc :: GenParser Char st ParsedPiece+parseFunc = parserFunc <?> "Call tag"+ where parserFunc = do+ pos <- getPosition+ string "${" >> spaces+ exp <- parseExpression (try $ spaces >> string "}")+ return $ ParsedPiece (FuncPiece exp) pos++parseExpression :: GenParser Char st a -> GenParser Char st ParsedExpression+parseExpression end = manyPart <?> "Expression"+ where parseGroup = try parens <|> parseAtomic+ parseAtomic = do+ pos <- getPosition+ exp <- try parseList <|> + try (parseString '\"') <|> + try (parseString '\'') <|> + try (parseFloat >>= (return . FloatExpression)) <|> + try (parseInt >>= (return . IntegerExpression)) <|>+ literal+ return $ ParsedExpression exp pos+ parens = (string "(" >> spaces) >> parseExpression (try $ spaces >> string ")")+ parseList = between (string "[" >> spaces) + (spaces >> string "]") + (sepBy (spaces >> parseGroup) (string ",")) + >>= (return . ListExpression)+ manyPart = do+ pos <- getPosition+ pieces <- manyTill (spaces >> parseGroup) end+ return $ ParsedExpression (Multipart pieces) pos+ parseString esc = parseStringContents esc >>= (return . StringExpression)+ literal = wordString >>= (return . LookupExpression)++parseStringContents :: Char -> GenParser Char st String+parseStringContents esc = between (char esc) (char esc) (many chars)+ where chars = (try escaped) <|> noneOf [esc]+ escaped = char '\\' >> choice (zipWith escapedChar codes replacements)+ escapedChar code replacement = char code >> return replacement+ codes = ['b', 'n', 'f', 'r', 't', '\\', '\"', '\'', '/']+ replacements = ['\b', '\n', '\f', '\r', '\t', '\\', '\"', '\'', '/']++parseStatic :: GenParser Char st ParsedPiece+parseStatic = do+ pos <- getPosition+ c <- anyChar+ s <- manyTill anyChar (peekChar '{' <|> peekChar '$' <|> eof)+ return $ ParsedPiece (StaticPiece (B.fromString $ c:s)) pos++parseNonStatic :: GenParser Char st ParsedPiece+parseNonStatic = try parseComment+ <|> try parseRaw+ <|> try parseBlock+ <|> try parseFor+ <|> try parseInclude+ <|> parseFunc+ +parsePiece :: GenParser Char st ParsedPiece+parsePiece = (try parseNonStatic <|> parseStatic)++parsePieces :: GenParser Char st [ParsedPiece]+parsePieces = parseContent eof++parseToTemplate :: GenParser Char st Template+parseToTemplate = (parseContent eof) >>= (return . Template)++-- | Take a template body and a template name and return either an error or a+-- renderable template.++parseTemplate :: String -> String -> Either ParseError Template+parseTemplate s tmplN = parse parseToTemplate tmplN s
+ src/Text/Strapped/Render.hs view
@@ -0,0 +1,132 @@+module Text.Strapped.Render + ( combineBuckets+ , varBucket+ , bucketLookup+ , bucketFromList+ , emptyBucket+ , render+ , defaultConfig+ ) where++import Blaze.ByteString.Builder+import Blaze.ByteString.Builder.Char.Utf8+import Control.Monad+import qualified Data.Map as M+import Data.List (intersperse)+import Data.Monoid ((<>), mempty, mconcat)+import Control.Monad.Except+import Control.Monad.IO.Class+import Data.Maybe (catMaybes)+import qualified Data.Text.Lazy as T+import Text.Strapped.Types+import Text.Parsec.Pos+++instance Renderable Builder where+ renderOutput _ = id++instance Renderable Literal where+ renderOutput (RenderConfig _ ef) (LitText s) = fromLazyText $ ef s+ renderOutput _ (LitSafe s) = fromLazyText s+ renderOutput rc (LitInteger i) = fromShow i+ renderOutput rc (LitDouble i) = fromShow i+ renderOutput _ (LitBuilder b) = b+ renderOutput rc (LitList l) = (fromChar '[') <> + (mconcat $ intersperse (fromChar ',') (map (renderOutput rc) l)) <> + (fromChar ']')+ renderOutput rc (LitDyn r) = renderOutput rc r+ +-- | Default render configuration. No text escaping.+defaultConfig :: RenderConfig+defaultConfig = RenderConfig (\_ -> return Nothing) id++-- | If the first bucket fails, try the second.+combineBuckets :: InputBucket m -> InputBucket m -> InputBucket m+combineBuckets = (++) ++-- | Basic bucket. Matches on string and return input. Returns Nothing for+-- everything else.+varBucket :: String -> Input m -> InputBucket m+varBucket varName o = [M.fromList [(varName, o)]]++emptyBucket :: InputBucket m+emptyBucket = []++bucketLookup :: String -> InputBucket m -> Maybe (Input m)+bucketLookup v [] = Nothing+bucketLookup v (m:ms) = maybe (bucketLookup v ms) Just (M.lookup v m)++bucketFromList :: [(String, Input m)] -> InputBucket m+bucketFromList l = [M.fromList l]++getOrThrow v getter pos = maybe (throwError $ InputNotFound v pos) return (bucketLookup v getter)++reduceExpression :: Monad m => RenderConfig -> ParsedExpression -> InputBucket m -> ExceptT StrapError m Literal+reduceExpression c (ParsedExpression exp pos) getter = convert exp+ where convertMore exp = reduceExpression c exp getter+ convert (IntegerExpression i) = return $ LitInteger i+ convert (FloatExpression i) = return $ LitDouble i+ convert (StringExpression s) = return $ LitText (T.pack s)+ convert (Multipart []) = return $ LitEmpty+ convert (Multipart (f:[])) = convertMore f+ convert (Multipart ((ParsedExpression (LookupExpression func) ipos):args)) = do+ val <- getOrThrow func getter pos+ case val of+ (Func f) -> convert (Multipart args) >>= f+ _ -> throwError $ StrapError ("`" ++ func ++ "` is not a function but has args: " ++ (show args)) ipos+ convert (Multipart v) = throwError $ StrapError ("`" ++ (show v) ++ "` cannot be reduced.") pos+ convert (ListExpression args) = mapM convertMore args >>= (return . LitList) + convert (LookupExpression f) = do+ val <- getOrThrow f getter pos+ inputToLiteral val+ inputToLiteral inp = case inp of+ (List args) -> mapM inputToLiteral args >>= (return . LitList)+ (RenderVal r) -> return $ LitBuilder (renderOutput c r)+ (Func f) -> f LitEmpty+ (LitVal v) -> return v++-- | Using a 'TemplateStore' and an 'InputBucket' render the template name.+render :: MonadIO m => RenderConfig -> InputBucket m -> String -> m (Either StrapError Output)+render renderConfig getter' tmplName = do+ tmpl <- liftIO $ tmplStore tmplName+ maybe (return $ Left $ TemplateNotFound tmplName (initialPos tmplName)) + (\(Template c) -> runExceptT $ loop mempty mempty getter' c) + tmpl+ where tmplStore = templateStore renderConfig+ loop accum _ _ [] = return accum+ loop accum blks getter ((ParsedPiece (StaticPiece s) pos):ps) =+ loop (accum <> s) blks getter ps+ loop accum blks getter ((ParsedPiece (BlockPiece n def) pos):ps) = + (maybe (loop accum blks getter def) + (\content -> loop accum blks getter content)+ (lookup n blks)+ ) >>= (\a -> loop a blks getter ps)+ loop accum blks getter ((ParsedPiece (ForPiece n exp c) pos):ps) = do+ var <- reduceExpression renderConfig exp getter+ case var of + LitList l -> (processFor getter n c accum blks l) >>= (\a -> loop a blks getter ps)+ _ -> throwError $ StrapError ("`" ++ show exp ++ "` is not a LitList") pos+ loop accum blks getter ((ParsedPiece (Inherits n b) pos):ps) =+ liftIO (tmplStore n) >>=+ maybe (throwError (TemplateNotFound n pos))+ (\(Template c) -> (loop accum (b ++ blks) getter c) >>= + (\a -> loop a blks getter ps))+ loop accum blks getter ((ParsedPiece (Include n) pos):ps) =+ liftIO (tmplStore n) >>=+ maybe (throwError (TemplateNotFound n pos)) + (\(Template c) -> (loop accum blks getter c) >>=+ (\a -> loop a blks getter ps))+ loop accum blks getter ((ParsedPiece (Decl n exp) pos):ps) = + (reduceExpression renderConfig exp getter) >>=+ (\v -> loop accum blks (combineBuckets (varBucket n (LitVal v)) getter) ps)++ loop accum blks getter ((ParsedPiece (FuncPiece exp) pos):ps) = + (reduceExpression renderConfig exp getter) >>= + (\r -> loop (accum <> (renderOutput renderConfig r)) blks getter ps) + + processFor getter varName content accum blks objs = loopFor accum objs+ where loopGetter o = combineBuckets (varBucket varName (LitVal o)) getter+ loopFor accum [] = return accum+ loopFor accum (o:os) = do+ s <- loop accum blks (loopGetter o) content+ loopFor s os
+ src/Text/Strapped/Types.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE ExistentialQuantification #-}+module Text.Strapped.Types where++import Blaze.ByteString.Builder+import Control.Monad.Except+import Data.List (intersperse)+import qualified Data.Map as M+import Data.Monoid (mconcat)+import Data.Text.Lazy (Text)+import Data.Typeable+import Text.Parsec.Pos++type Output = Builder++instance Show Builder where+ show = show . toByteString++data Expression = + LookupExpression String |+ IntegerExpression Integer |+ FloatExpression Double |+ StringExpression String |+ ListExpression [ParsedExpression] |+ Multipart [ParsedExpression]++instance Show Expression where+ show (LookupExpression s) = s+ show (IntegerExpression s) = show s+ show (FloatExpression s) = show s+ show (StringExpression s) = "\"" ++ s ++ "\""+ show (ListExpression l) = "[" ++ (mconcat $ intersperse "," (map show l)) ++ "]"+ show (Multipart l) = mconcat $ map show l++data ParsedExpression = ParsedExpression Expression SourcePos++instance Show ParsedExpression where+ show (ParsedExpression exp _) = show exp++data Piece = StaticPiece Output+ | BlockPiece String [ParsedPiece]+ | ForPiece String ParsedExpression [ParsedPiece]+ | FuncPiece ParsedExpression+ | Decl String ParsedExpression+ | Include String+ | Inherits String [(String, [ParsedPiece])]+ deriving (Show)++data ParsedPiece = ParsedPiece Piece SourcePos+ deriving (Show)+ +class Renderable a where+ renderOutput :: RenderConfig -> a -> Output+ +data Input m = forall a . (Renderable a) => RenderVal a+ | List [Input m]+ | Func (Literal -> ExceptT StrapError m Literal)+ | LitVal Literal++data Literal = forall a . (Typeable a, Renderable a) => LitDyn a+ | LitText Text+ | LitSafe Text+ | LitInteger Integer+ | LitDouble Double+ | LitBuilder Builder+ | LitList ![Literal]+ | LitEmpty++data StrapError = StrapError String SourcePos + | InputNotFound String SourcePos + | TemplateNotFound String SourcePos+ deriving (Show, Eq)++type InputBucket m = [M.Map String (Input m)]++type TemplateStore = String -> IO (Maybe Template)++data Template = Template [ParsedPiece]+ deriving Show++data RenderConfig = RenderConfig + { templateStore :: TemplateStore+ , escapeFunc :: Text -> Text+ }
+ src/Text/Strapped/Utils.hs view
@@ -0,0 +1,33 @@+module Text.Strapped.Utils where++import Blaze.ByteString.Builder+import Blaze.ByteString.Builder.Char.Utf8+import Control.Monad+import Data.List hiding (find)+import Text.Strapped.Types+import Text.Strapped.Parser++import System.FilePath.Find+import System.FilePath.Posix (addTrailingPathSeparator)++import Text.ParserCombinators.Parsec++templateStoreFromList :: [(String, String)] -> Either ParseError TemplateStore+templateStoreFromList tmpls = do+ templateTups <- forM tmpls (\(tn, t) -> fmap ((,) tn) $ parseTemplate t tn)+ return (\n -> return $ lookup n templateTups)++-- | Given a file path and extension, load all templates in a directory, recursively.+templateStoreFromDirectory :: FilePath -> String -> IO (Either ParseError TemplateStore)+templateStoreFromDirectory dir ext = do+ files <- find always (extension ==? ext) dirPath+ tmpls <- forM files (\fn -> let fname = maybe [] id $ stripPrefix dirPath fn + in print fname >> readFile fn >>= (return . (,) fname))+ return $ templateStoreFromList tmpls+ where dirPath = addTrailingPathSeparator dir++putStore :: TemplateStore -> RenderConfig -> RenderConfig+putStore ts rc = rc { templateStore = ts } ++showToBuilder :: Show a => a -> Builder+showToBuilder = fromShow
+ tests/StrappedTest.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE OverloadedStrings #-}++import Test.Hspec++import qualified Blaze.ByteString.Builder as B+import qualified Data.Text.Lazy as T+import qualified Data.ByteString as BS+import Text.Strapped+import System.Exit++data Custom = Custom+ deriving (Show)++instance Renderable Custom where+ renderOutput _ c = showToBuilder c++mainBucket :: InputBucket IO+mainBucket = bucketFromList [+ ("custom", RenderVal Custom),+ ("is", List $ map (LitVal . LitInteger) [1..3]),+ ("addNumbers", Func add)+ ]+ where add (LitList ((LitInteger a):(LitInteger b):[])) = return $ LitInteger $ a + b+ add (LitList ((LitInteger a):(LitInteger b):bs)) = add $ LitList ((LitInteger (a + b)):bs)+ add _ = return $ LitText $ T.pack $ "Only list of ints."++renderShouldBe :: IO (Either StrapError Output) -> Either StrapError BS.ByteString -> Expectation+renderShouldBe f c = f >>= (\e -> (fmap B.toByteString e) `shouldBe` c)++spec :: RenderConfig -> Spec+spec ts = do+ describe "for loop" $+ it "Should loop through stuff" $ do+ (render ts mainBucket "for_loop") `renderShouldBe` (Right "1, 2, 3, ")++ describe "RenderVal" $+ it "class RenderVal" $ do+ (render ts mainBucket "custom") `renderShouldBe` (Right "Custom")++ describe "Comment" $+ it "Check comments" $ do+ (render ts mainBucket "comment") `renderShouldBe` (Right "")++ describe "Raw" $+ it "Check raw" $ do+ (render ts mainBucket "raw") `renderShouldBe` (Right "${ custom }")++ describe "Let" $+ it "Check let" $ do+ (render ts mainBucket "let") `renderShouldBe` (Right "Custom")++ describe "Include" $+ it "Check include" $ do+ (render ts mainBucket "include") `renderShouldBe` (Right "Custom")++ describe "Inherit" $+ it "Check inherits" $ do+ (render ts mainBucket "inherits") `renderShouldBe` (Right "Some base override")++ describe "Function" $+ it "Checks Function" $ + (render ts mainBucket "func") `renderShouldBe` (Right "4")+++main = do+ let ets = templateStoreFromList + [ ("for_loop", "{$ for i in is $}${ i }, {$ endfor $}")+ , ("custom", "${ custom }")+ , ("comment", "{$ comment $}${ custom }{$ endcomment $}")+ , ("raw", "{$ raw $}${ custom }{$ endraw $}")+ , ("let", "{$ let i = custom $}\n${ i }")+ , ("include", "{$ include custom $}")+ , ("base", "Some base {$ block some_block $}blah blah {$ endblock $}")+ , ("inherits", "{$ inherits base $}{$ isblock some_block $}override{$ endisblock $}")+ , ("func", "${ addNumbers [(addNumbers [1, 1]), 2] }")+ ]+ either (\_ -> exitFailure) (\ts -> hspec $ spec (defaultConfig {templateStore = ts})) ets