{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} {-# LANGUAGE RankNTypes #-} -- Module: Text.Hastache.Context -- Copyright: Sergey S Lymar (c) 2011-2013 -- License: BSD3 -- Maintainer: Sergey S Lymar -- Stability: experimental -- Portability: portable {- | Hastache context helpers -} module Text.Hastache.Context ( mkStrContext , mkStrContextM , mkGenericContext , mkGenericContext' , Ext , defaultExt ) where import Data.Data import Data.Generics import Data.Int import Data.Version (Version) import Data.Ratio (Ratio) import Data.Word import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS import qualified Data.Map as Map import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as TL 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 ~> return -- | Make Hastache context from monadic String -> MuType function mkStrContextM :: Monad m => (String -> m (MuType m)) -> MuContext m mkStrContextM f a = decodeStr a ~> f type Ext = forall b. (Data b, Typeable b) => b -> String -- | @defaultExt ==@ 'gshow' defaultExt :: Ext defaultExt = gshow {- | Make Hastache context from Data.Data deriving type Supported field types: * () * String * Char * Double * Float * Int * Int8 * Int16 * Int32 * Int64 * Integer * Word * Word8 * Word16 * Word32 * Word64 * Data.ByteString.ByteString * Data.ByteString.Lazy.ByteString * Data.Text.Text * Data.Text.Lazy.Text * Bool * Version * Maybe @a@ (where @a@ is a supported datatype) * Either @a@ @b@ (where @a@ and @b@ are supported datatypes) * Data.Text.Text -> Data.Text.Text * Data.Text.Text -> Data.Text.Lazy.Text * Data.Text.Lazy.Text -> Data.Text.Lazy.Text * Data.ByteString.ByteString -> Data.ByteString.ByteString * String -> String * Data.ByteString.ByteString -> Data.ByteString.Lazy.ByteString * MonadIO m => Data.Text.Text -> m Data.Text.Text * MonadIO m => Data.Text.Text -> m Data.Text.Lazy.Text * MonadIO m => Data.Text.Lazy.Text -> m Data.Text.Lazy.Text * MonadIO m => Data.ByteString.ByteString -> m Data.ByteString.ByteString * MonadIO m => String -> m String * MonadIO m => Data.ByteString.ByteString -> m Data.ByteString.Lazy.ByteString Example: @ import Text.Hastache import Text.Hastache.Context import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.IO as TL import Data.Data import Data.Generics import Data.Char data InternalData = InternalData { someField :: String, anotherField :: Int } deriving (Data, Typeable, Show) data Example = Example { stringField :: String, intField :: Int, dataField :: InternalData, simpleListField :: [String], dataListField :: [InternalData], stringFunc :: String -> String, textFunc :: T.Text -> T.Text, monadicStringFunc :: String -> IO String, monadicTextFunc :: T.Text -> IO T.Text } deriving (Data, Typeable) example = hastacheStr defaultConfig (encodeStr template) (mkGenericContext context) where template = unlines [ \"string: {{stringField}}\", \"int: {{intField}}\", \"data: {{dataField.someField}}, {{dataField.anotherField}}\", \"data: {{#dataField}}{{someField}}, {{anotherField}}{{/dataField}}\", \"simple list: {{#simpleListField}}{{.}} {{/simpleListField}}\", \"data list:\", \"{{#dataListField}}\", \" * {{someField}}, {{anotherField}}. top level var: {{intField}}\", \"{{/dataListField}}\", \"{{#stringFunc}}upper{{/stringFunc}}\", \"{{#textFunc}}reverse{{/textFunc}}\", \"{{#monadicStringFunc}}upper (monadic){{/monadicStringFunc}}\", \"{{#monadicTextFunc}}reverse (monadic){{/monadicTextFunc}}\"] context = Example { stringField = \"string value\", intField = 1, dataField = InternalData \"val\" 123, simpleListField = [\"a\",\"b\",\"c\"], dataListField = [InternalData \"aaa\" 1, InternalData \"bbb\" 2], stringFunc = map toUpper, textFunc = T.reverse, monadicStringFunc = return . map toUpper, monadicTextFunc = return . T.reverse } main = example >>= TL.putStrLn @ Result: @ string: string value int: 1 data: val, 123 data: val, 123 simple list: a b c data list: * aaa, 1. top level var: 1 * bbb, 2. top level var: 1 UPPER esrever UPPER (MONADIC) )cidanom( esrever @ Hastache also supports datatypes with multiple constructors: @ data A = A { str :: String } | B { num :: Int } {{#A}} A : {{str}} {{/A}} {{#B}} B : {{num}} {{/B}} @ -} #if MIN_VERSION_base(4,7,0) mkGenericContext :: (Monad m, Data a, Typeable m) => a -> MuContext m #else mkGenericContext :: (Monad m, Data a, Typeable1 m) => a -> MuContext m #endif mkGenericContext val = toGenTemp id defaultExt val ~> convertGenTempToContext {-| Like 'mkGenericContext', but apply the first function to record field names when constructing the context. The second function is used to constructing values for context from datatypes that are nor supported as primitives in the library. The resulting value can be accessed using the @.DatatypeName@ field: @ \{\-\# LANGUAGE DeriveDataTypeable \#\-\} \{\-\# LANGUAGE FlexibleInstances \#\-\} \{\-\# LANGUAGE ScopedTypeVariables \#\-\} \{\-\# LANGUAGE StandaloneDeriving \#\-\} \{\-\# LANGUAGE TypeSynonymInstances \#\-\} import Text.Hastache import Text.Hastache.Context import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.IO as TL import Data.Data (Data, Typeable) import Data.Decimal import Data.Generics.Aliases (extQ) data Test = Test {n::Int, m::Decimal} deriving (Data, Typeable) deriving instance Data Decimal val :: Test val = Test 1 (Decimal 3 1500) q :: Ext q = defaultExt \`extQ\` (\(i::Decimal) -> "A decimal: " ++ show i) r "m" = "moo" r x = x example :: IO TL.Text example = hastacheStr defaultConfig (encodeStr template) (mkGenericContext' r q val) template = concat [ "{{n}}\\n", "{{moo.Decimal}}" ] main = example >>= TL.putStrLn @ Result: @ 1 A decimal: 1.500 @ -} #if MIN_VERSION_base(4,7,0) mkGenericContext' :: (Monad m, Data a, Typeable m) => (String -> String) -> Ext -> a -> MuContext m #else mkGenericContext' :: (Monad m, Data a, Typeable1 m) => (String -> String) -> Ext -> a -> MuContext m #endif mkGenericContext' f ext val = toGenTemp f ext val ~> convertGenTempToContext data TD m = TSimple (MuType m) | TObj [(String, TD m)] | TList [TD m] | TUnknown deriving (Show) #if MIN_VERSION_base(4,7,0) toGenTemp :: (Data a, Monad m, Typeable m) => (String -> String) -> Ext -> a -> TD m #else toGenTemp :: (Data a, Monad m, Typeable1 m) => (String -> String) -> Ext -> a -> TD m #endif toGenTemp f g a = TObj $ conName : zip fields (gmapQ (procField f g) a) where fields = toConstr a ~> constrFields ~> map f conName = (toConstr a ~> showConstr, TSimple . MuVariable $ g a) #if MIN_VERSION_base(4,7,0) procField :: (Data a, Monad m, Typeable m) => (String -> String) -> Ext -> a -> TD m #else procField :: (Data a, Monad m, Typeable1 m) => (String -> String) -> Ext -> a -> TD m #endif procField f g a = case res a of TUnknown -> TSimple . MuVariable . g $ a b -> b where res = 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::T.Text) -> MuVariable i ~> TSimple) `extQ` (\(i::TL.Text) -> MuVariable i ~> TSimple) `extQ` (\(i::Bool) -> MuBool i ~> TSimple) `extQ` (\() -> MuVariable () ~> TSimple) `extQ` (\(i::Version) -> MuVariable i ~> TSimple) `extQ` muLambdaTT `extQ` muLambdaTTL `extQ` muLambdaTLTL `extQ` muLambdaBSBS `extQ` muLambdaSS `extQ` muLambdaBSLBS `extQ` muLambdaMTT `extQ` muLambdaMTTL `extQ` muLambdaMTLTL `extQ` muLambdaMBSBS `extQ` muLambdaMSS `extQ` muLambdaMBSLBS `ext1Q` muMaybe `ext2Q` muEither obj a = case dataTypeRep (dataTypeOf a) of AlgRep (_:_) -> toGenTemp f g a _ -> TUnknown list a = map (procField f g) a ~> TList muMaybe Nothing = TSimple MuNothing muMaybe (Just a) = TList [procField f g a] muEither (Left a) = procField f g a muEither (Right b) = procField f g b muLambdaTT :: (T.Text -> T.Text) -> TD m muLambdaTT f = MuLambda f ~> TSimple muLambdaTLTL :: (TL.Text -> TL.Text) -> TD m muLambdaTLTL f = MuLambda (f . TL.fromStrict) ~> TSimple muLambdaTTL :: (T.Text -> TL.Text) -> TD m muLambdaTTL f = MuLambda f ~> TSimple muLambdaBSBS :: (BS.ByteString -> BS.ByteString) -> TD m muLambdaBSBS f = MuLambda (f . T.encodeUtf8) ~> TSimple muLambdaBSLBS :: (BS.ByteString -> LBS.ByteString) -> TD m muLambdaBSLBS f = MuLambda (f . T.encodeUtf8) ~> TSimple muLambdaSS :: (String -> String) -> TD m muLambdaSS f = MuLambda fd ~> TSimple where fd s = decodeStr s ~> f -- monadic muLambdaMTT :: (T.Text -> m T.Text) -> TD m muLambdaMTT f = MuLambdaM f ~> TSimple muLambdaMTLTL :: (TL.Text -> m TL.Text) -> TD m muLambdaMTLTL f = MuLambdaM (f . TL.fromStrict) ~> TSimple muLambdaMTTL :: (T.Text -> m TL.Text) -> TD m muLambdaMTTL f = MuLambdaM f ~> TSimple muLambdaMBSBS :: (BS.ByteString -> m BS.ByteString) -> TD m muLambdaMBSBS f = MuLambdaM (f . T.encodeUtf8) ~> TSimple muLambdaMBSLBS :: (BS.ByteString -> m LBS.ByteString) -> TD m muLambdaMBSLBS f = MuLambdaM (f . T.encodeUtf8) ~> TSimple muLambdaMSS :: (String -> m String) -> TD m muLambdaMSS f = MuLambdaM fd ~> TSimple where fd s = decodeStr s ~> f convertGenTempToContext :: Monad m => TD m -> MuContext m 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 ~> Map.insert (encodeStr name) ([foldl (foldTObj "") Map.empty lst ~> mkMapContext] ~> MuList) 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 = return $ case Map.lookup a m of Nothing -> case a == dotT of True -> case Map.lookup T.empty m of Nothing -> MuNothing Just a -> a _ -> MuNothing Just a -> a dotT :: T.Text dotT = T.singleton '.'