hastache (empty) → 0.1.1
raw patch · 9 files changed
+918/−0 lines, 9 filesdep +basedep +bytestringdep +containerssetup-changed
Dependencies added: base, bytestring, containers, directory, filepath, mtl, syb, text, utf8-string
Files
- LICENSE +30/−0
- README.markdown +13/−0
- Setup.hs +5/−0
- Text/Hastache.hs +355/−0
- Text/Hastache/Context.hs +147/−0
- hastache.cabal +46/−0
- tests/RunTest.sh +2/−0
- tests/partFile +1/−0
- tests/test.hs +319/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2011, Sergey S Lymar++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. 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.++3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE 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 AUTHORS 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.
+ README.markdown view
@@ -0,0 +1,13 @@+# Hastache++Haskell implementation of [Mustache templates](http://mustache.github.com/)++## Installation++ cabal update+ cabal install hastache++## Examples++See tests/test.hs for examples of usage+
+ Setup.hs view
@@ -0,0 +1,5 @@+#! /usr/bin/env runhaskell++import Distribution.Simple+main = defaultMain+
+ Text/Hastache.hs view
@@ -0,0 +1,355 @@+{-# LANGUAGE ExistentialQuantification, FlexibleInstances, FlexibleContexts,+ IncoherentInstances #-}+-- Module: Text.Hastache+-- Copyright: Sergey S Lymar (c) 2011 +-- License: BSD3+-- Maintainer: Sergey S Lymar <sergey.lymar@gmail.com>+-- Stability: experimental+-- Portability: portable+--+-- Haskell implementation of Mustache templates++{- | Haskell implementation of Mustache templates++See homepage for examples of usage: <http://github.com/lymar/hastache>+-}+module Text.Hastache (+ hastacheStr+ , hastacheFile+ , MuContext+ , MuType(..)+ , MuConfig(..)+ , MuVar+ , htmlEscape+ , emptyEscape+ , defaultConfig+ , encodeStr+ , encodeStrLBS+ , decodeStr+ , decodeStrLBS+ ) where ++import Prelude hiding (putStrLn, readFile, length, drop, tail, dropWhile, + elem, head, last, reverse, take, span)+import Data.ByteString hiding (map, foldl1)+import qualified Data.ByteString.Lazy as LZ+import qualified Codec.Binary.UTF8.String as SU+import Control.Monad.Writer.Lazy (tell, liftIO, MonadIO, execWriterT,+ MonadWriter)+import Data.Char (ord)+import Data.Word+import Control.Monad (guard, when)+import System.FilePath (combine)+import System.Directory (doesFileExist)+import Control.Monad.Trans (lift)+import Data.Int+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText+import Data.Maybe (isJust)++(~>) :: a -> (a -> b) -> b+x ~> f = f $ x+infixl 9 ~>++-- | Data for Hastache variable+type MuContext m = + ByteString -- ^ Variable name+ -> MuType m -- ^ Value++class Show a => MuVar a where+ toLByteString :: a -> LZ.ByteString+ +instance MuVar ByteString where+ toLByteString = toLBS+ +instance MuVar LZ.ByteString where+ toLByteString = id+ +withShowToLBS a = show a ~> encodeStr ~> toLBS++instance MuVar Integer where toLByteString = withShowToLBS+instance MuVar Int where toLByteString = withShowToLBS+instance MuVar Float where toLByteString = withShowToLBS+instance MuVar Double where toLByteString = withShowToLBS+instance MuVar Int8 where toLByteString = withShowToLBS+instance MuVar Int16 where toLByteString = withShowToLBS+instance MuVar Int32 where toLByteString = withShowToLBS+instance MuVar Int64 where toLByteString = withShowToLBS+instance MuVar Word where toLByteString = withShowToLBS+instance MuVar Word8 where toLByteString = withShowToLBS+instance MuVar Word16 where toLByteString = withShowToLBS+instance MuVar Word32 where toLByteString = withShowToLBS+instance MuVar Word64 where toLByteString = withShowToLBS++instance MuVar Text.Text where + toLByteString t = Text.unpack t ~> encodeStr ~> toLBS++instance MuVar LText.Text where + toLByteString t = LText.unpack t ~> encodeStr ~> toLBS+ +instance MuVar Char where+ toLByteString a = (a : "") ~> encodeStr ~> toLBS++instance MuVar a => MuVar [a] where+ toLByteString a = (toLByteString '[') <+> cnvLst <+> (toLByteString ']')+ where+ cnvLst = (map toLByteString a) ~> + (LZ.intercalate (toLByteString ','))+ (<+>) = LZ.append++instance MuVar [Char] where+ toLByteString k = k ~> encodeStr ~> toLBS+ +data MuType m = + forall a. MuVar a => MuVariable a |+ MuList [MuContext m] |+ MuBool Bool |+ MuLambda (ByteString -> ByteString) |+ MuLambdaM (ByteString -> m ByteString)+ +instance Show (MuType m) where+ show (MuVariable a) = "MuVariable " ++ show a+ show (MuList _) = "MuList [..]"+ show (MuBool v) = "MuBool " ++ show v+ show (MuLambda _) = "MuLambda <..>"+ show (MuLambdaM _) = "MuLambdaM <..>"++data MuConfig = MuConfig {+ muEscapeFunc :: LZ.ByteString -> LZ.ByteString, + -- ^ Escape function ('htmlEscape', 'emptyEscape' etc.)+ muTemplateFileDir :: Maybe FilePath,+ -- ^ Directory for search partial templates ({{> templateName}})+ muTemplateFileExt :: Maybe String+ -- ^ Partial template files extension+ }++-- | Convert String to UTF-8 Bytestring+encodeStr :: String -> ByteString+encodeStr = pack . SU.encode++-- | Convert String to UTF-8 Lazy Bytestring+encodeStrLBS :: String -> LZ.ByteString+encodeStrLBS = LZ.pack . SU.encode++-- | Convert UTF-8 Bytestring to String+decodeStr :: ByteString -> String+decodeStr = SU.decode . unpack++-- | Convert UTF-8 Lazy Bytestring to String+decodeStrLBS :: LZ.ByteString -> String+decodeStrLBS = SU.decode . LZ.unpack++ord8 :: Char -> Word8+ord8 = fromIntegral . ord++-- | Escape HTML symbols+htmlEscape :: LZ.ByteString -> LZ.ByteString+htmlEscape str = LZ.unpack str ~> proc ~> LZ.pack+ where+ proc :: [Word8] -> [Word8]+ proc (h:t)+ | h == ord8 '&' = stp "&" t+ | h == ord8 '\\'= stp "\\\\" t+ | h == ord8 '"' = stp """ t+ | h == ord8 '\''= stp "'" t+ | h == ord8 '<' = stp "<" t+ | h == ord8 '>' = stp ">" t+ | otherwise = h : (proc t)+ proc [] = []+ stp a t = (map ord8 a) ++ (proc t)++-- | No escape+emptyEscape :: LZ.ByteString -> LZ.ByteString+emptyEscape = id++{- | Default config: HTML escape function, current directory as + template directory, template file extension not specified -}+defaultConfig :: MuConfig +defaultConfig = MuConfig { + muEscapeFunc = htmlEscape,+ muTemplateFileDir = Nothing,+ muTemplateFileExt = Nothing+ } ++defOTag = encodeStr "{{"+defCTag = encodeStr "}}"+unquoteCTag = encodeStr "}}}"++findBlock :: ByteString -> ByteString -> ByteString+ -> Maybe (ByteString, Word8, ByteString, ByteString)+findBlock str otag ctag = do+ guard (length fnd > (length otag))+ Just (pre, symb, inTag, afterClose)+ where+ (pre, fnd) = breakSubstring otag str+ symb = index fnd (length otag)+ (inTag, afterClose)+ -- test for unescape ( {{{some}}} )+ | symb == ord8 '{' && ctag == defCTag = + breakSubstring unquoteCTag fnd ~> \(a,b) -> + (drop (length otag) a, drop 3 b)+ | otherwise = breakSubstring ctag fnd ~> \(a,b) -> + (drop (length otag) a, drop (length ctag) b)++toLBS :: ByteString -> LZ.ByteString+toLBS v = LZ.fromChunks [v]++readVar context name = do+ case context name of+ MuVariable a -> toLByteString a+ MuBool a -> show a ~> encodeStr ~> toLBS+ _ -> LZ.empty+ +findCloseSection :: ByteString -> ByteString -> ByteString -> ByteString+ -> Maybe (ByteString, ByteString)+findCloseSection str name otag ctag = do+ guard (length after > 0)+ Just (before, drop (length close) after)+ where+ close = foldl1 append [otag, encodeStr "/", name, ctag]+ (before, after) = breakSubstring close str++trimCharsTest :: Word8 -> Bool+trimCharsTest = (`elem` (encodeStr " \t"))++trimAll :: ByteString -> ByteString+trimAll str = span trimCharsTest str ~> snd ~> spanEnd trimCharsTest ~> fst++tellBS :: (MonadWriter LZ.ByteString m) => ByteString -> m ()+tellBS str = toLBS str ~> tell++processBlock str context otag ctag conf = do+ case findBlock str otag ctag of+ Just (pre, symb, inTag, afterClose) -> do+ tellBS pre+ renderBlock context symb inTag afterClose + otag ctag conf+ Nothing -> do+ tellBS str+ return ()++renderBlock context symb inTag afterClose otag ctag conf+ -- comment+ | symb == ord8 '!' = next afterClose+ -- unescape variable+ | symb == ord8 '&' || (symb == ord8 '{' && otag == defOTag) = do+ readVar context (tail inTag ~> trimAll) ~> tell+ next afterClose+ -- section. inverted section+ | symb == ord8 '#' || symb == ord8 '^' = + let normalSection = symb == ord8 '#' in do+ case findCloseSection afterClose (tail inTag) otag ctag of+ Nothing -> next afterClose+ Just (sectionContent', afterSection') -> + let + dropNL str = + if (length str) > 0 && (head str) == ord8 '\n' + then tail str+ else str+ sectionContent = dropNL sectionContent'+ afterSection = + if ord8 '\n' `elem` sectionContent+ then dropNL afterSection'+ else afterSection'+ in do+ case context (tail inTag) of+ MuList [] -> + if normalSection then do next afterSection+ else do+ processBlock sectionContent+ context otag ctag conf+ next afterSection+ MuList b -> + if normalSection then do+ mapM_ (\c -> processBlock sectionContent+ c otag ctag conf) b+ next afterSection+ else do next afterSection+ MuBool True -> + if normalSection then do+ processBlock sectionContent+ context otag ctag conf+ next afterSection+ else do next afterSection+ MuBool False -> + if normalSection then do next afterSection+ else do+ processBlock sectionContent+ context otag ctag conf+ next afterSection+ MuLambda func -> + if normalSection then do+ func sectionContent ~> tellBS+ next afterSection+ else do next afterSection+ MuLambdaM func -> + if normalSection then do+ res <- lift (func sectionContent)+ tellBS res+ next afterSection+ else do next afterSection+ _ -> next afterSection+ -- set delimiter+ | symb == ord8 '=' = + let+ lenInTag = length inTag+ delimitersCommand = take (lenInTag - 1) inTag ~> drop 1+ getDelimiter = do+ guard (lenInTag > 4)+ guard ((index inTag $ lenInTag - 1) == ord8 '=')+ [newOTag,newCTag] <- Just $ split (ord8 ' ') + delimitersCommand+ Just (newOTag, newCTag)+ in do+ case getDelimiter of+ Nothing -> next afterClose+ Just (newOTag, newCTag) -> + processBlock (trim' afterClose) context + newOTag newCTag conf+ -- partials+ | symb == ord8 '>' =+ let + fileName' = tail inTag ~> trimAll+ fileName'' = case muTemplateFileExt conf of+ Nothing -> fileName'+ Just ext -> fileName' `append` (encodeStr ext)+ fileName = decodeStr fileName''+ fullFileName = case muTemplateFileDir conf of+ Nothing -> fileName+ Just path -> combine path fileName+ in do+ fe <- liftIO $ doesFileExist fullFileName+ when fe $ do+ cnt <- liftIO $ readFile fullFileName+ next cnt+ next (trim' afterClose)+ -- variable+ | otherwise = do+ readVar context (trimAll inTag) ~> muEscapeFunc conf ~> tell+ next afterClose+ where+ next t = processBlock t context otag ctag conf+ trim' content = + dropWhile trimCharsTest content+ ~> \t -> if (length t > 0 && head t == ord8 '\n')+ then tail t else content++-- | Render Hastache template from ByteString+hastacheStr :: (MonadIO m) => + MuConfig -- ^ Configuration+ -> ByteString -- ^ Template+ -> MuContext m -- ^ Context+ -> m LZ.ByteString+hastacheStr conf str context = + execWriterT (processBlock str context defOTag defCTag conf)++-- | Render Hastache template from file+hastacheFile :: (MonadIO m) => + MuConfig -- ^ Configuration+ -> String -- ^ Template file name+ -> MuContext m -- ^ Context+ -> m LZ.ByteString+hastacheFile conf file_name context = do+ str <- liftIO $ readFile file_name+ hastacheStr conf str context+
+ Text/Hastache/Context.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- Module: Text.Hastache.Context+-- Copyright: Sergey S Lymar (c) 2011 +-- License: BSD3+-- Maintainer: Sergey S Lymar <sergey.lymar@gmail.com>+-- Stability: experimental+-- Portability: portable++{- | +Hastache context helpers+-}+module Text.Hastache.Context (+ mkStrContext+ , mkGenericContext+ ) where ++import Data.Data+import Data.Generics+import Data.Int+import Data.Word++import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText+import qualified Data.Map as Map++import Text.Hastache++x ~> f = f $ x+infixl 9 ~>++-- | Make Hastache context from String -> MuType function+mkStrContext :: Monad m => (String -> MuType m) -> MuContext m+mkStrContext f a = decodeStr a ~> f++{- | +Make Hastache context from Data.Data deriving type++@+data InternalData = InternalData {+ someField :: String,+ anotherField :: Int+ } deriving (Data, Typeable, Show)++data Example = Example {+ stringField :: String,+ intField :: Int,+ dataField :: InternalData,+ simpleListField :: [String],+ dataListField :: [InternalData]+ } deriving (Data, Typeable, Show)++example = hastacheStr defaultConfig (encodeStr template) + (mkGenericContext context)+ where+ template = concat [+ \"string: {{stringField}} \\n\",+ \"int: {{intField}} \\n\",+ \"data: {{dataField.someField}}, {{dataField.anotherField}} \\n\",+ \"simple list: {{#simpleListField}}{{.}} {{/simpleListField}} \\n\",+ \"data list: \\n\",+ \"{{#dataListField}}\\n\",+ \" * {{someField}}, {{anotherField}} \\n\",+ \"{{/dataListField}}\\n\"]+ context = Example { stringField = \"string value\", intField = 1, + dataField = InternalData \"val\" 123, simpleListField = [\"a\",\"b\",\"c\"],+ dataListField = [InternalData \"aaa\" 1, InternalData \"bbb\" 2] }+@++Result:++@+string: string value +int: 1 +data: val, 123 +simple list: a b c +data list: + * aaa, 1 + * bbb, 2 +@+-}+mkGenericContext :: (Monad m, Data a) => a -> MuContext m+mkGenericContext val = toGenTemp val ~> convertGenTempToContext+ +data TD m = + TSimple (MuType m) + | TObj [(String, TD m)] + | TList [TD m] + | TUnknown+ deriving (Show)++toGenTemp :: (Data a, Monad m) => a -> TD m+toGenTemp a = zip fields (gmapQ procField a) ~> TObj+ where+ fields = toConstr a ~> constrFields++procField :: (Data a, Monad m) => a -> TD m+procField = + obj+ `ext1Q` list+ `extQ` (\(i::String) -> MuVariable (encodeStr i) ~> TSimple)+ `extQ` (\(i::Char) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Double) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Float) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Int) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Int8) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Int16) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Int32) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Int64) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Integer) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Word) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Word8) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Word16) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Word32) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Word64) -> MuVariable i ~> TSimple)+ `extQ` (\(i::BS.ByteString) -> MuVariable i ~> TSimple)+ `extQ` (\(i::LBS.ByteString) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Text.Text) -> MuVariable i ~> TSimple)+ `extQ` (\(i::LText.Text) -> MuVariable i ~> TSimple)+ `extQ` (\(i::Bool) -> MuBool i ~> TSimple)+ where+ obj a = case dataTypeRep (dataTypeOf a) of+ AlgRep [c] -> toGenTemp a+ _ -> TUnknown+ list a = map procField a ~> TList++convertGenTempToContext :: TD t -> MuContext t+convertGenTempToContext v = mkMap "" Map.empty v ~> mkMapContext+ where+ mkMap name m (TSimple t) = Map.insert (encodeStr name) t m+ mkMap name m (TObj lst) = foldl (foldTObj name) m lst+ mkMap name m (TList lst) = Map.insert (encodeStr name) + (map convertGenTempToContext lst ~> MuList) m+ mkMap _ m _ = m+ + mkName name newName = if length name > 0 + then concat [name, ".", newName]+ else newName+ foldTObj name m (fn, fv) = mkMap (mkName name fn) m fv+ + mkMapContext m a = case Map.lookup a m of+ Nothing -> case Map.lookup BS.empty m of+ Nothing -> MuVariable BS.empty+ Just a -> a+ Just a -> a+
+ hastache.cabal view
@@ -0,0 +1,46 @@+name: hastache+version: 0.1.1+license: BSD3+license-file: LICENSE+category: Text+copyright: Sergey S Lymar (c) 2011+author: Sergey S Lymar <sergey.lymar@gmail.com>+maintainer: Sergey S Lymar <sergey.lymar@gmail.com>+stability: experimental+tested-with: GHC == 7.0.2+synopsis: Haskell implementation of Mustache templates+cabal-version: >= 1.8+homepage: http://github.com/lymar/hastache+bug-reports: http://github.com/lymar/hastache/issues+build-type: Simple+description:+ Haskell implementation of Mustache templates (<http://mustache.github.com/>).+ .+ See homepage for examples of usage: <http://github.com/lymar/hastache>++extra-source-files:+ tests/partFile+ tests/RunTest.sh+ tests/test.hs+ README.markdown+ +library+ exposed-modules:+ Text.Hastache+ Text.Hastache.Context++ build-depends:+ base == 4.*+ ,bytestring+ ,mtl+ ,directory+ ,filepath+ ,utf8-string+ ,text+ ,containers+ ,syb++source-repository head+ type: git+ location: http://github.com/lymar/hastache+
+ tests/RunTest.sh view
@@ -0,0 +1,2 @@+runhaskell -i.. test.hs +
+ tests/partFile view
@@ -0,0 +1,1 @@+Hi, {{name}}!
+ tests/test.hs view
@@ -0,0 +1,319 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Tests where++import Test.HUnit+import Text.Hastache+import Text.Hastache.Context+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LZ+import qualified Data.Text as T+import Control.Monad+import Control.Monad.Writer+import Data.Data+import Data.Generics++-- Hastache comments+commentsTest = do+ res <- hastacheStr defaultConfig (encodeStr template) undefined+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \hello {{! comment #1}} world! \n\+ \hello {{! comment #2 \n\+ \multiline\n\+ \}} world! \n\+ \"+ testRes = "\+ \hello world! \n\+ \hello world! \n\+ \"++-- Variables+variablesTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \ Char: [ {{Char}} ] \n\+ \ Double: [ {{Double}} ] \n\+ \ Int: [ {{Int}} ] \n\+ \ ByteString: [ {{ByteString}} ] \n\+ \ Text: [ {{Text}} ] \n\+ \ String: [ {{String}} ] \n\+ \ HtmlString: [ {{HtmlString}} ] \n\+ \ HtmlStringUnEsc: [ {{{HtmlString}}} ] \n\+ \ HtmlStringUnEsc2: [ {{&HtmlString}} ] \n\+ \"+ context "Char" = MuVariable 'Й'+ context "Double" = MuVariable (123.45 :: Double)+ context "Int" = MuVariable (5 :: Int)+ context "ByteString" = MuVariable (encodeStr "hello - привет")+ context "Text" = MuVariable (T.pack "hello - привет")+ context "String" = MuVariable "hello - привет"+ context "HtmlString" = MuVariable "<p>text</p>"+ + testRes = "\+ \ Char: [ Й ] \n\+ \ Double: [ 123.45 ] \n\+ \ Int: [ 5 ] \n\+ \ ByteString: [ hello - привет ] \n\+ \ Text: [ hello - привет ] \n\+ \ String: [ hello - привет ] \n\+ \ HtmlString: [ <p>text</p> ] \n\+ \ HtmlStringUnEsc: [ <p>text</p> ] \n\+ \ HtmlStringUnEsc2: [ <p>text</p> ] \n\+ \"++-- Show/hide block according to list state+emptyListSectionsTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \text 1\n\+ \{{#blankSection}}\n\+ \ some text\n\+ \{{/blankSection}}\n\+ \text 2\n\+ \{{^blankSection}}\n\+ \ empty list. {{someval}}\n\+ \{{/blankSection}}\n\+ \inline [{{#blankSection}}txt{{/blankSection}}]\n\+ \"+ context "blankSection" = MuList []+ context "someval" = MuVariable (5 :: Int)+ + testRes = "\+ \text 1\n\+ \text 2\n\+ \ empty list. 5\n\+ \inline []\n\+ \"++-- Render list+listSectionTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \text 1\n\+ \{{#section}}\n\+ \ * {{name}} \n\+ \{{/section}}\n\+ \text 2\n\+ \inline {{#section}}[{{name}}]{{/section}}\n\+ \"+ context "section" = MuList $ map nameCtx ["Neo", "Morpheus", "Trinity"]+ nameCtx name = mkStrContext (\"name" -> MuVariable name)+ + testRes = "\+ \text 1\n\+ \ * Neo \n\+ \ * Morpheus \n\+ \ * Trinity \n\+ \text 2\n\+ \inline [Neo][Morpheus][Trinity]\n\+ \"++-- Show/hide block according to boolean variable+boolSectionTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \text 1\n\+ \{{#bool_true}}\n\+ \ true: {{someval}} \n\+ \{{/bool_true}}\n\+ \{{^bool_true}}\n\+ \ true inv: {{someval}} \n\+ \{{/bool_true}}\n\+ \{{#bool_false}}\n\+ \ false: {{someval}} \n\+ \{{/bool_false}}\n\+ \{{^bool_false}}\n\+ \ false inv: {{someval}} \n\+ \{{/bool_false}}\n\+ \text 2\n\+ \"+ context "bool_true" = MuBool True+ context "bool_false" = MuBool False+ context "someval" = MuVariable "val"+ + testRes = "\+ \text 1\n\+ \ true: val \n\+ \ false inv: val \n\+ \text 2\n\+ \"++-- Transorm section+lambdaSectionTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \text 1\n\+ \{{#function}}Hello{{/function}}\n\+ \text 2\n\+ \"+ context "function" = MuLambda BS.reverse+ + testRes = "\+ \text 1\n\+ \olleH\n\+ \text 2\n\+ \"++-- Transform section with monadic function+lambdaMSectionTest = do+ (res, writerState) <- runWriterT monadicFunction+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ assertEqualStr "monad state correctness" (decodeStr writerState)+ testMonad+ where+ monadicFunction = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ return res+ template = "\+ \[{{#mf}}abc{{/mf}}]\n\+ \[{{#mf}}def{{/mf}}]\n\+ \"+ context "mf" = MuLambdaM $ \i -> do+ tell i+ return $ BS.reverse i+ testRes = "\+ \[cba]\n\+ \[fed]\n\+ \"+ testMonad = "abcdef"++-- Change delimiters+setDelimiterTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \text 1\n\+ \{{someVar}}\n\+ \{{=<% %>=}}\n\+ \<%someVar%>\n\+ \<%={{ }}=%>\n\+ \{{someVar}}\n\+ \text 2\n\+ \"+ context "someVar" = MuVariable "some value"+ + testRes = "\+ \text 1\n\+ \some value\n\+ \some value\n\+ \some value\n\+ \text 2\n\+ \"++-- Render external (partial) template file+partialsTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkStrContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \text 1\n\+ \{{> partFile}}\n\+ \text 2\n\+ \"+ context "name" = MuVariable "Neo"+ + testRes = "\+ \text 1\n\+ \Hi, Neo!\n\+ \text 2\n\+ \"++data InternalData = InternalData {+ intDataField1 :: String,+ intDataField2 :: Int+ }+ deriving (Data, Typeable, Show)+ +data SomeData = SomeData {+ someDataField1 :: String,+ someDataInternal :: InternalData,+ someDataList :: [Int],+ someDataObjList :: [InternalData]+ }+ deriving (Data, Typeable, Show)++-- Make hastache context from Data.Data deriving type+genericContextTest = do+ res <- hastacheStr defaultConfig (encodeStr template) + (mkGenericContext context)+ assertEqualStr "result correctness" (decodeStrLBS res) testRes+ where+ template = "\+ \text 1\n\+ \{{someDataField1}} {{someDataInternal.intDataField1}} \n\+ \{{someDataInternal.intDataField2}} \n\+ \Simple list:\n\+ \{{#someDataList}}\n\+ \* {{.}} \n\+ \{{/someDataList}}\n\+ \Obj list:\n\+ \{{#someDataObjList}}\n\+ \* {{intDataField1}} : {{intDataField2}} \n\+ \{{/someDataObjList}}\n\+ \text 2\n\+ \"+ context = SomeData {+ someDataField1 = "aaa",+ someDataInternal = InternalData { + intDataField1 = "zzz", intDataField2 = 100 },+ someDataList = [1,2,3],+ someDataObjList = [InternalData "a" 1, InternalData "b" 2,+ InternalData "c" 3]+ }+ + testRes = "\+ \text 1\n\+ \aaa zzz \n\+ \100 \n\+ \Simple list:\n\+ \* 1 \n\+ \* 2 \n\+ \* 3 \n\+ \Obj list:\n\+ \* a : 1 \n\+ \* b : 2 \n\+ \* c : 3 \n\+ \text 2\n\+ \"++tests = TestList [+ TestLabel "Comments test" (TestCase commentsTest)+ ,TestLabel "Variables test" (TestCase variablesTest)+ ,TestLabel "Empty list test" (TestCase emptyListSectionsTest)+ ,TestLabel "List test" (TestCase listSectionTest)+ ,TestLabel "Bool test" (TestCase boolSectionTest)+ ,TestLabel "Lambda test" (TestCase lambdaSectionTest)+ ,TestLabel "LambdaM test" (TestCase lambdaMSectionTest)+ ,TestLabel "Set delimiter test" (TestCase setDelimiterTest)+ ,TestLabel "Partials test" (TestCase partialsTest)+ ,TestLabel "Generic context test" (TestCase genericContextTest)+ ]++main = do+ runTestTT tests++assertEqualStr preface expected actual =+ unless (actual == expected) (assertFailure msg)+ where msg = (if null preface then "" else preface ++ "\n") +++ "expected: \n" ++ expected ++ "\nbut got: \n" ++ actual+