packages feed

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 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+  .+  @+  &#160;$&#x7b; ioTime &#x7d;+  &#160;+  &#160;&#x7b;$ for i in is $&#x7d;+  &#160;    $&#x7b; i &#x7d;+  &#160;&#x7b;$ endfor $&#x7d;+  @++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