packages feed

HSmarty 0.2.0.3 → 0.4.0

raw patch · 6 files changed

+282/−131 lines, 6 filesdep +Globdep +bytestringdep +filepathdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: Glob, bytestring, filepath

Dependency ranges changed: base

API changes (from Hackage documentation)

- Text.HSmarty: data TemplateParam
- Text.HSmarty: renderTemplate :: FilePath -> ParamMap -> IO (Either SmartyError Text)
- Text.HSmarty: unSmartyError :: SmartyError -> Text
- Text.HSmarty.Types: f_args :: FunctionCall -> [(Text, Expr)]
- Text.HSmarty.Types: f_body :: Foreach -> [SmartyStmt]
- Text.HSmarty.Types: f_else :: Foreach -> Maybe [SmartyStmt]
- Text.HSmarty.Types: f_item :: Foreach -> Text
- Text.HSmarty.Types: f_key :: Foreach -> Maybe Text
- Text.HSmarty.Types: f_name :: FunctionCall -> Text
- Text.HSmarty.Types: f_source :: Foreach -> Expr
- Text.HSmarty.Types: if_cases :: If -> [(Expr, [SmartyStmt])]
- Text.HSmarty.Types: if_else :: If -> Maybe [SmartyStmt]
- Text.HSmarty.Types: instance Eq BinOp
- Text.HSmarty.Types: instance Eq Expr
- Text.HSmarty.Types: instance Eq Foreach
- Text.HSmarty.Types: instance Eq FunctionCall
- Text.HSmarty.Types: instance Eq If
- Text.HSmarty.Types: instance Eq Smarty
- Text.HSmarty.Types: instance Eq SmartyStmt
- Text.HSmarty.Types: instance Eq Variable
- Text.HSmarty.Types: instance Show BinOp
- Text.HSmarty.Types: instance Show Expr
- Text.HSmarty.Types: instance Show Foreach
- Text.HSmarty.Types: instance Show FunctionCall
- Text.HSmarty.Types: instance Show If
- Text.HSmarty.Types: instance Show Smarty
- Text.HSmarty.Types: instance Show SmartyStmt
- Text.HSmarty.Types: instance Show Variable
- Text.HSmarty.Types: s_name :: Smarty -> FilePath
- Text.HSmarty.Types: s_template :: Smarty -> [SmartyStmt]
- Text.HSmarty.Types: v_index :: Variable -> Maybe Expr
- Text.HSmarty.Types: v_name :: Variable -> Text
- Text.HSmarty.Types: v_path :: Variable -> [Text]
- Text.HSmarty.Types: v_prop :: Variable -> Maybe Text
+ Text.HSmarty: [unSmartyError] :: SmartyError -> Text
+ Text.HSmarty: applyTemplateFromJson :: ToJSON a => FilePath -> SmartyCtx -> a -> Either SmartyError Text
+ Text.HSmarty: prepareTemplates :: String -> FilePath -> IO SmartyCtx
+ Text.HSmarty.Parser.Smarty: pCapture :: Parser Capture
+ Text.HSmarty.Parser.Smarty: pFunDef :: Parser FunctionDef
+ Text.HSmarty.Parser.Smarty: pLet :: Parser Let
+ Text.HSmarty.Parser.Smarty: pScope :: Parser Scope
+ Text.HSmarty.Types: Capture :: Text -> [SmartyStmt] -> Capture
+ Text.HSmarty.Types: FunctionDef :: Text -> [(Text, Expr)] -> [SmartyStmt] -> FunctionDef
+ Text.HSmarty.Types: Let :: Text -> Expr -> Let
+ Text.HSmarty.Types: Scope :: [SmartyStmt] -> Scope
+ Text.HSmarty.Types: SmartyCapture :: Capture -> SmartyStmt
+ Text.HSmarty.Types: SmartyFun :: FunctionDef -> SmartyStmt
+ Text.HSmarty.Types: SmartyLet :: Let -> SmartyStmt
+ Text.HSmarty.Types: SmartyScope :: Scope -> SmartyStmt
+ Text.HSmarty.Types: [c_name] :: Capture -> Text
+ Text.HSmarty.Types: [c_stmts] :: Capture -> [SmartyStmt]
+ Text.HSmarty.Types: [f_args] :: FunctionCall -> [(Text, Expr)]
+ Text.HSmarty.Types: [f_body] :: Foreach -> [SmartyStmt]
+ Text.HSmarty.Types: [f_else] :: Foreach -> Maybe [SmartyStmt]
+ Text.HSmarty.Types: [f_item] :: Foreach -> Text
+ Text.HSmarty.Types: [f_key] :: Foreach -> Maybe Text
+ Text.HSmarty.Types: [f_name] :: FunctionCall -> Text
+ Text.HSmarty.Types: [f_source] :: Foreach -> Expr
+ Text.HSmarty.Types: [fd_body] :: FunctionDef -> [SmartyStmt]
+ Text.HSmarty.Types: [fd_defArgs] :: FunctionDef -> [(Text, Expr)]
+ Text.HSmarty.Types: [fd_name] :: FunctionDef -> Text
+ Text.HSmarty.Types: [if_cases] :: If -> [(Expr, [SmartyStmt])]
+ Text.HSmarty.Types: [if_else] :: If -> Maybe [SmartyStmt]
+ Text.HSmarty.Types: [l_expr] :: Let -> Expr
+ Text.HSmarty.Types: [l_name] :: Let -> Text
+ Text.HSmarty.Types: [s_name] :: Smarty -> FilePath
+ Text.HSmarty.Types: [s_stmts] :: Scope -> [SmartyStmt]
+ Text.HSmarty.Types: [s_template] :: Smarty -> [SmartyStmt]
+ Text.HSmarty.Types: [v_index] :: Variable -> Maybe Expr
+ Text.HSmarty.Types: [v_name] :: Variable -> Text
+ Text.HSmarty.Types: [v_path] :: Variable -> [Text]
+ Text.HSmarty.Types: [v_prop] :: Variable -> Maybe Text
+ Text.HSmarty.Types: data Capture
+ Text.HSmarty.Types: data FunctionDef
+ Text.HSmarty.Types: data Let
+ Text.HSmarty.Types: data Scope
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.BinOp
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.Capture
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.Expr
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.Foreach
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.FunctionCall
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.FunctionDef
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.If
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.Let
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.Scope
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.Smarty
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.SmartyStmt
+ Text.HSmarty.Types: instance GHC.Classes.Eq Text.HSmarty.Types.Variable
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.BinOp
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.Capture
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.Expr
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.Foreach
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.FunctionCall
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.FunctionDef
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.If
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.Let
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.Scope
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.Smarty
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.SmartyStmt
+ Text.HSmarty.Types: instance GHC.Show.Show Text.HSmarty.Types.Variable
- Text.HSmarty: applyTemplate :: SmartyCtx -> ParamMap -> IO (Either SmartyError Text)
+ Text.HSmarty: applyTemplate :: FilePath -> SmartyCtx -> ParamMap -> Either SmartyError Text
- Text.HSmarty: mkParam :: ToJSON a => a -> TemplateParam
+ Text.HSmarty: mkParam :: ToJSON a => a -> Value
- Text.HSmarty: type ParamMap = HashMap Text TemplateParam
+ Text.HSmarty: type ParamMap = HashMap Text Value
- Text.HSmarty.Parser.Smarty: parseSmarty :: Monad m => FilePath -> Text -> m Smarty
+ Text.HSmarty.Parser.Smarty: parseSmarty :: MonadFail m => FilePath -> Text -> m Smarty
- Text.HSmarty.Parser.Util: stripSpace :: Parser c -> Parser c
+ Text.HSmarty.Parser.Util: stripSpace :: forall c. Parser c -> Parser c

Files

HSmarty.cabal view
@@ -1,5 +1,5 @@ name:                HSmarty-version:             0.2.0.3+version:             0.4.0 synopsis:            Small template engine description:         Haskell implementation of a subset of the PHP-Smarty template language Homepage:            https://github.com/agrafix/HSmarty@@ -8,12 +8,13 @@ license-file:        LICENSE author:              Alexander Thiemann <mail@athiemann.net> maintainer:          Alexander Thiemann <mail@athiemann.net>-copyright:           (c) 2013 - 2015 by Alexander Thiemann+copyright:           (c) 2013 - 2020 by Alexander Thiemann category:            Text build-type:          Simple-cabal-version:       >=1.8+cabal-version:       >=1.10 data-dir:            data data-files:          test.tpl+tested-with:         GHC==8.8.4  Library   hs-source-dirs:    src@@ -21,26 +22,31 @@                      Text.HSmarty.Parser.Smarty,                      Text.HSmarty.Parser.Util,                      Text.HSmarty.Types-  other-modules:     Text.HSmarty.Render.Engine+  other-modules:     Text.HSmarty.Render.Engine, Paths_HSmarty   Ghc-Options:       -Wall+  default-language:  Haskell2010   build-depends:                      HTTP,                      aeson >=0.8,                      attoparsec >=0.11,                      attoparsec-expr >=0.1.1,-                     base >= 4 && < 5,+                     base >= 4.8 && < 5,                      mtl >=2.2,                      scientific >=0.3,                      text >=1.2,                      unordered-containers >=0.2,-                     vector >=0.10+                     vector >=0.10,+                     Glob >= 0.7,+                     filepath,+                     bytestring  Test-Suite TestHSmarty   hs-source-dirs:    test-  other-modules:     Text.HSmarty.Parser.SmartyTest+  other-modules:     Text.HSmarty.Parser.SmartyTest, Paths_HSmarty   Type:              exitcode-stdio-1.0   Main-Is:           Tests.hs   Ghc-Options:       -Wall+  default-language:  Haskell2010   build-depends:                      HSmarty,                      HTF,
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2013, Alexander Thiemann <mail@agrafix.net>+Copyright (c) 2013 - 2020, Alexander Thiemann <mail@thiemann.at>  All rights reserved. 
src/Text/HSmarty/Parser/Smarty.hs view
@@ -2,18 +2,18 @@ {-# LANGUAGE RankNTypes #-} module Text.HSmarty.Parser.Smarty where -import Text.HSmarty.Types import Text.HSmarty.Parser.Util+import Text.HSmarty.Types +import Control.Applicative import Data.Attoparsec.Text import Data.Char-import Control.Applicative  import qualified Data.Aeson as A import qualified Data.Attoparsec.Expr as E import qualified Data.Text as T -parseSmarty :: Monad m => FilePath -> T.Text -> m Smarty+parseSmarty :: MonadFail m => FilePath -> T.Text -> m Smarty parseSmarty fp t =     either fail mk $ parseOnly pRoot t     where@@ -30,6 +30,9 @@     SmartyText <$> pLiteral <|>     SmartyIf <$> pIf <|>     SmartyForeach <$> pForeach <|>+    SmartyCapture <$> pCapture <|>+    SmartyScope <$> pScope <|>+    SmartyFun <$> pFunDef <|>     braced (char '{') (char '}') (SmartyPrint <$> pExpr <*> many pPrintDirective) <|>     SmartyText <$> (takeWhile1 (/='{')) @@ -67,7 +70,7 @@  pName :: Parser T.Text pName =-    identP isAlpha isAlphaNum+    identP (\c -> isAlpha c || c == '_') (\c -> isAlphaNum c || c == '_')  pLiteral :: Parser T.Text pLiteral =@@ -96,6 +99,33 @@ pClose :: T.Text -> Parser T.Text pClose t =     string $ T.concat [ "{/", t, "}" ]++pLet :: Parser Let+pLet =+    Let+    <$> (string "{$" *> pName <* char '=')+    <*> pExpr <* char '}'++pScope :: Parser Scope+pScope =+    Scope <$> (pOpen "scope" *> many pStmt <* pClose "scope")++pCapture :: Parser Capture+pCapture =+    Capture+    <$> ((string "{capture name=" *> space_) *> stringP <* char '}')+    <*> (many pStmt <* pClose "capture")++pFunDef :: Parser FunctionDef+pFunDef =+    FunctionDef+    <$> ((string "{function name=" *> space_) *> stringP)+    <*> (many pArg <* char '}')+    <*> (many pStmt <* pClose "function")+    where+      pArg =+          (,) <$> (space_ *> pName <* stripSpace (char '='))+              <*> pExpr  pIf :: Parser If pIf =
src/Text/HSmarty/Render/Engine.hs view
@@ -1,36 +1,39 @@+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE DoAndIfThenElse #-} module Text.HSmarty.Render.Engine-    ( TemplateParam, ParamMap-    , mkParam+    ( ParamMap, mkParam     , SmartyCtx, SmartyError(..)-    , prepareTemplate, applyTemplate-    , renderTemplate+    , prepareTemplate, prepareTemplates+    , applyTemplate+    , applyTemplateFromJson     ) where -import Text.HSmarty.Types+import Control.Monad import Text.HSmarty.Parser.Smarty+import Text.HSmarty.Types+import qualified Data.Text.Lazy as TL -import Data.Scientific-import Control.Applicative import Control.Monad.Except+import Control.Monad.Identity import Data.Char (ord) import Data.Maybe+import Data.Scientific import Data.Vector ((!?)) import Network.HTTP.Base (urlEncode)+import System.FilePath+import System.FilePath.Glob import qualified Data.Aeson as A+import qualified Data.ByteString.Lazy as BSL import qualified Data.HashMap.Strict as HM import qualified Data.Text as T+import qualified Data.Text.Encoding as T import qualified Data.Text.IO as T+import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Vector as V --- | An template param, construct using 'mkParam'-newtype TemplateParam-      = TemplateParam { unTemplateParam :: A.Value }-        deriving (Show, Eq)- data TemplateVar    = TemplateVar    { tv_value :: A.Value@@ -38,57 +41,98 @@    }    deriving (Show, Eq) -type Env = HM.HashMap T.Text TemplateVar- -- | Maps template variables to template params-type ParamMap = HM.HashMap T.Text TemplateParam+type ParamMap = HM.HashMap T.Text A.Value type PropMap = HM.HashMap T.Text A.Value -type EvalM a = ExceptT SmartyError IO a+mkParam :: A.ToJSON a => a -> A.Value+mkParam = A.toJSON +type EvalM m a = ExceptT SmartyError m a+ newtype SmartyCtx-    = SmartyCtx { _unSmartyCtx :: Smarty }-      deriving (Show, Eq)+    = SmartyCtx+    { unSmartyCtx :: HM.HashMap FilePath Smarty+    } deriving (Show, Eq) +data Env =+    Env+    { e_var :: HM.HashMap T.Text TemplateVar+    , e_fun :: HM.HashMap T.Text ([SmartyStmt], [(T.Text, A.Value)])+    , e_ctx :: SmartyCtx+    } deriving (Show, Eq)+ newtype SmartyError     = SmartyError { unSmartyError :: T.Text }       deriving (Show, Eq) --- | Pack a value as a template param-mkParam :: A.ToJSON a => a -> TemplateParam-mkParam = TemplateParam . A.toJSON--mkEnv :: ParamMap -> Env-mkEnv =-    HM.map (\init' -> TemplateVar (unTemplateParam init') HM.empty)+mkEnv :: ParamMap -> SmartyCtx -> Env+mkEnv pm ctx =+    Env+    { e_var = HM.map (\init' -> TemplateVar init' HM.empty) pm+    , e_fun = HM.empty+    , e_ctx = ctx+    }  -- | Parse and compile a template prepareTemplate :: FilePath -> IO SmartyCtx prepareTemplate fp =     do ct <- T.readFile fp-       SmartyCtx <$> parseSmarty fp ct+       tpl <- HM.singleton fp <$> parseSmarty fp ct+       pure $ SmartyCtx tpl +-- | Parse and compiles templates matching a glob in a directiry+prepareTemplates :: String -> FilePath -> IO SmartyCtx+prepareTemplates pat dir =+    do files <- globDir1 (compile pat) dir+       let dirDropper =+               makeRelative dir+       ctx <-+           foldM (\hm f ->+                      do ct <- T.readFile f >>= parseSmarty f+                         pure (HM.insert (dirDropper f) ct hm)+                 ) mempty files+       pure $ SmartyCtx ctx+ -- | Fill a template with values and print it as Text-applyTemplate :: SmartyCtx -> ParamMap -> IO (Either SmartyError T.Text)-applyTemplate (SmartyCtx ctx) mp =-    runExceptT $ evalTpl (mkEnv mp) ctx+applyTemplateFromJson :: A.ToJSON a => FilePath -> SmartyCtx -> a -> Either SmartyError T.Text+applyTemplateFromJson a b c =+  case A.toJSON c of+    A.Object hm -> runIdentity $ runExceptT $ applyTemplate' a b hm+    x -> Left (SmartyError $ T.pack $ show x ++ " is not a json object, need an object at top level") --- | Render a template using the specified ParamMap.--- Results in either an error-message or the rendered template.--- DO NOT USE IN Production. Use `prepareTemplate` and `applyTemplate` instead.-renderTemplate :: FilePath -> ParamMap -> IO (Either SmartyError T.Text)-renderTemplate fp mp =-    do ctx <- prepareTemplate fp-       applyTemplate ctx mp+-- | Fill a template with values and print it as Text+applyTemplate :: FilePath -> SmartyCtx -> ParamMap -> Either SmartyError T.Text+applyTemplate a b c =+    runIdentity $ runExceptT $ applyTemplate' a b c -applyPrintDirective :: T.Text -> PrintDirective -> EvalM T.Text-applyPrintDirective t "urlencode" =-    return $ T.pack $ urlEncode $ T.unpack t-applyPrintDirective t "nl2br" =-    return $ T.replace "\n" "<br />" t-applyPrintDirective t "escape" =-    return $ T.pack $ htmlEscape $ T.unpack t+applyTemplate' :: Monad m => FilePath -> SmartyCtx -> ParamMap -> ExceptT SmartyError m T.Text+applyTemplate' template ctx mp =+    do getTpl <-+           case HM.lookup template (unSmartyCtx ctx) of+             Just ok -> pure ok+             Nothing ->+                 throwError (SmartyError $ T.pack $ "Template " ++ template ++ " not compiled")+       evalTpl (mkEnv mp ctx) getTpl++txtPdHelper ::+    Monad m => Env -> Expr -> (T.Text -> T.Text) -> ExceptT SmartyError m Expr+txtPdHelper env expr go =+    do t <- exprToText env expr+       return $ ExprLit $ A.String $ go t++applyPrintDirective :: Monad m => Env -> Expr -> PrintDirective -> EvalM m Expr+applyPrintDirective env expr "json" =+    do evaled <- evalExpr env expr+       pure $ ExprLit $ A.String $ T.decodeUtf8 $ BSL.toStrict $ A.encode evaled+applyPrintDirective env expr "urlencode" =+    txtPdHelper env expr (T.pack . urlEncode . T.unpack)+applyPrintDirective env expr "nl2br" =+    txtPdHelper env expr (T.replace "\n" "<br />")+applyPrintDirective env expr "escape" =+    txtPdHelper env expr (T.pack . htmlEscape . T.unpack)     where+      forbidden :: String       forbidden = "<&\">'/"       htmlEscape :: String -> String       htmlEscape [] = []@@ -98,59 +142,85 @@                       , htmlEscape xs                       ]           else x : htmlEscape xs-applyPrintDirective _ pd =+applyPrintDirective _ _ pd =     throwError $ SmartyError $     T.concat [ "Unknown print directive `"              , pd              , "`"              ] -evalTpl :: Env -> Smarty -> EvalM T.Text+evalTpl :: Monad m => Env -> Smarty -> EvalM m T.Text evalTpl env (Smarty _ tpl) =-    evalBody env tpl+    snd <$> seqStmts env tpl -evalStmt :: Env -> SmartyStmt -> EvalM T.Text-evalStmt _ (SmartyText t) = return t-evalStmt _ (SmartyComment _) = return T.empty+evalStmt :: Monad m => Env -> SmartyStmt -> EvalM m (Env, T.Text)+evalStmt env (SmartyScope sc) =+    do (_, body) <- seqStmts env (s_stmts sc)+       pure (env, body)+evalStmt env (SmartyFun fd) =+    do args <-+           forM (fd_defArgs fd) $ \(n, expr) ->+           do r <- evalExpr env expr+              pure (n, r)+       let fun =+               HM.insert (fd_name fd) (fd_body fd, args) (e_fun env)+       pure (env { e_fun = fun }, T.empty)+evalStmt env (SmartyCapture cap) =+    do (_, body) <- seqStmts env (c_stmts cap)+       let eVars =+               HM.insert (c_name cap) (TemplateVar (A.String body) mempty) (e_var env)+       pure (env { e_var = eVars }, T.empty)+evalStmt env (SmartyLet l) =+    do r <- evalExpr env (l_expr l)+       let eVars =+               HM.insert (l_name l) (TemplateVar r mempty) (e_var env)+       pure (env { e_var = eVars }, T.empty)+evalStmt env (SmartyText t) = return (env, t)+evalStmt env (SmartyComment _) = return (env, T.empty) evalStmt env (SmartyPrint expr directives) =-    do t <- exprToText env expr-       foldM applyPrintDirective t directives+    do e <- foldM (applyPrintDirective env) expr directives+       (,) <$> pure env <*> exprToText env e evalStmt env (SmartyIf (If cases elseBody)) =     do evaledCases <- mapM (\(cond, body) ->                                 do r <- evalExpr env cond-                                   b <- evalBody env body+                                   (_, b) <- seqStmts env body                                    case r of-                                     (A.Bool True) ->-                                         return $ Just b-                                     _ ->+                                     (A.Bool False) ->                                          return Nothing+                                     A.Null -> return Nothing+                                     (A.Array v) | null v -> return Nothing+                                     _ ->+                                         return $ Just b                            ) cases        case catMaybes evaledCases of          (x:_) ->-             return x+             return (env, x)          _ ->              case elseBody of                Just elseB ->-                   evalBody env elseB+                   (,) <$> pure env <*> (snd <$> seqStmts env elseB)                Nothing ->-                   return T.empty-+                   return (env, T.empty) evalStmt env (SmartyForeach (Foreach source mKey val body elseBody)) =     do evaledSource <- evalExpr env source        (preparedSource, size) <- mkForeachInput evaledSource        if size == 0        then case elseBody of-              Just b -> evalBody env b-              Nothing -> return T.empty+              Just b -> (,) <$> pure env <*> (snd <$> seqStmts env b)+              Nothing -> return (env, T.empty)        else do runs <- mapM (evalForeachBody env mKey val body) preparedSource-               return $ T.concat runs+               return (env, T.concat runs) -evalBody :: Env -> [SmartyStmt] -> EvalM T.Text-evalBody env stmt =-    do b <- mapM (evalStmt env) stmt-       return $ T.concat b+seqStmts :: Monad m => Env -> [SmartyStmt] -> EvalM m (Env, T.Text)+seqStmts env stmt =+    do let go (ebase, tb) st =+               do (e, t) <- evalStmt ebase st+                  pure (e, tb <> TLB.fromText t)+       (e', tb) <-+           foldM go (env, mempty) stmt+       return (e', TL.toStrict $ TLB.toLazyText tb) -exprToText :: Env -> Expr -> EvalM T.Text+exprToText :: Monad m => Env -> Expr -> EvalM m T.Text exprToText env expr =     do evaled <- evalExpr env expr        case evaled of@@ -164,17 +234,22 @@          A.Array a ->              return $ T.pack $ show a -evalForeachBody :: Env -> Maybe T.Text -> T.Text -> [ SmartyStmt ] -> ( A.Value, A.Value, PropMap ) -> EvalM T.Text-evalForeachBody env mKey item body (keyVal, itemVal, props) =-    let env' = HM.insert item (TemplateVar itemVal props) env+evalForeachBody ::+    Monad m+    => Env -> Maybe T.Text -> T.Text -> [ SmartyStmt ]+    -> ( A.Value, A.Value, PropMap )+    -> EvalM m T.Text+evalForeachBody envFull mKey item body (keyVal, itemVal, props) =+    let env = e_var envFull+        env' = HM.insert item (TemplateVar itemVal props) env         env'' =             case mKey of               Just key -> HM.insert key (TemplateVar keyVal HM.empty) env'               Nothing -> env'-    in evalBody env'' body+    in snd <$> seqStmts (envFull { e_var = env'' }) body  -mkForeachInput :: A.Value -> EvalM ( [ ( A.Value, A.Value, PropMap ) ], Int)+mkForeachInput :: Monad m => A.Value -> EvalM m ( [ ( A.Value, A.Value, PropMap ) ], Int) mkForeachInput (A.Array vec) =     return $ ( V.toList $ V.imap (\idx el ->                                       ( A.Number (fromIntegral idx)@@ -213,36 +288,22 @@       size = fromIntegral size'       idx = fromIntegral idx' -str :: T.Text -> A.Value -> EvalM T.Text+str :: Monad m => T.Text -> A.Value -> EvalM m T.Text str _ (A.String x) = return x str desc _ = throwError $ SmartyError $ T.concat [ "`", desc, "` is not a string!" ] -int :: T.Text -> A.Value -> EvalM Int-int desc (A.Number x) =-    case floatingOrInteger x of-      Left _ -> throwError $ SmartyError $ T.concat [ "`", desc, "` is not an integer!" ]-      Right x' -> return x'-int desc _ = throwError $ SmartyError $ T.concat [ "`", desc, "` is not an integer!" ]--dbl :: T.Text -> A.Value -> EvalM Double-dbl desc (A.Number x) =-    case floatingOrInteger x of-      Left x' -> return x'-      Right _ -> throwError $ SmartyError $ T.concat [ "`", desc, "` is not a double!" ]-dbl desc _ = throwError $ SmartyError $ T.concat [ "`", desc, "` is not a double!" ]--ifExists :: (Eq a, Show a) => T.Text -> a -> [(a, A.Value)] -> (A.Value -> EvalM b) -> EvalM b+ifExists :: (Eq a, Show a, Monad m) => T.Text -> a -> [(a, A.Value)] -> (A.Value -> EvalM m b) -> EvalM m b ifExists msg key env fun =     case lookup key env of       Just x -> fun x       Nothing ->           throwError $ SmartyError $ T.concat [ "`", T.pack $ show key, "` is not given. ", msg] -lookupStr :: T.Text -> T.Text -> [(T.Text, A.Value)] -> EvalM T.Text+lookupStr :: Monad m => T.Text -> T.Text -> [(T.Text, A.Value)] -> EvalM m T.Text lookupStr funName key env =     ifExists (T.concat ["Param for `", funName, "`"]) key env (str key) -evalFunCall :: Env -> T.Text -> [ (T.Text, Expr) ] -> EvalM A.Value+evalFunCall :: Monad m => Env -> T.Text -> [ (T.Text, Expr) ] -> EvalM m A.Value evalFunCall env "include" args =     do evaledArgs <- mapM (\(k, expr) ->                                do val <- evalExpr env expr@@ -252,28 +313,37 @@        let otherArgs = filter (\(k, _) ->                                    not $ k `elem` [ "include" ]                               ) evaledArgs-           asTplParams = HM.fromList $ map (\(k, v) -> (k, TemplateParam v)) otherArgs-       content <- liftIO $ renderTemplate (T.unpack filename) asTplParams-       case content of-         Right c ->-             return $ A.String c-         Left e ->-             throwError $ SmartyError $ T.concat ["Include failed. Error: ", unSmartyError e]-evalFunCall _ fname _ =-    throwError $ SmartyError $-    T.concat [ "Call to undefined function "-             , fname-             ]+           asTplParams = HM.fromList $ map (\(k, v) -> (k, v)) otherArgs+       A.String <$> applyTemplate' (T.unpack filename) (e_ctx env) asTplParams+evalFunCall env fname args =+    case HM.lookup fname (e_fun env) of+      Just (fBody, fDefArgs) ->+          do localArgs <-+                 forM args $ \(n, expr) ->+                 do r <- evalExpr env expr+                    pure (n, r)+             let myArgs =+                     fmap (\v -> TemplateVar v mempty) $+                     HM.fromList localArgs `HM.union` HM.fromList fDefArgs+                 callEnv =+                     myArgs `HM.union` e_var env+             (_, res) <- seqStmts (env { e_var = callEnv }) fBody+             return (A.String res)+      Nothing ->+          throwError $ SmartyError $+          T.concat [ "Call to undefined function "+                   , fname+                   ]  -evalExpr :: Env -> Expr -> EvalM A.Value+evalExpr :: Monad m => Env -> Expr -> EvalM m A.Value evalExpr _ (ExprLit v) = return v evalExpr env (ExprBin op) =     evalBinOp env op evalExpr env (ExprFun funCall) =     evalFunCall env (f_name funCall) (f_args funCall) evalExpr env (ExprVar v) =-    case HM.lookup varName env of+    case HM.lookup varName (e_var env) of       Just tplVar ->           case v of             (Variable { v_prop = Just propReq }) ->@@ -315,7 +385,7 @@     where       varName = v_name v -walkIndex :: T.Text -> A.Value -> A.Value -> EvalM A.Value+walkIndex :: Monad m => T.Text -> A.Value -> A.Value -> EvalM m A.Value walkIndex vname (A.Number idx) (A.Array arr) =     case arr !? (fromJust $ toBoundedInteger idx) of       Just val -> return val@@ -338,7 +408,7 @@              , "`. Index is not an integer or value not an array!"              ] -walkPath :: T.Text -> [T.Text] -> A.Value -> EvalM A.Value+walkPath :: Monad m => T.Text -> [T.Text] -> A.Value -> EvalM m A.Value walkPath _ [] val = return val walkPath vname (path:xs) (A.Object obj) =     case HM.lookup path obj of@@ -360,7 +430,7 @@              , "`"              ] -evalBinOp :: Env -> BinOp -> EvalM A.Value+evalBinOp :: Monad m => Env -> BinOp -> EvalM m A.Value evalBinOp env (BinEq a b) =     boolResOp (\x y -> return $ x == y) (a, b) env evalBinOp env (BinNot e) =@@ -392,7 +462,7 @@     calcOp "Div" (/) (x, y) env  -boolOp :: T.Text -> (Bool -> Bool -> Bool) -> (Expr, Expr) -> Env -> EvalM A.Value+boolOp :: Monad m => T.Text -> (Bool -> Bool -> Bool) -> (Expr, Expr) -> Env -> EvalM m A.Value boolOp d op exprs env =     boolResOp bOp exprs env     where@@ -400,17 +470,17 @@           return $ a `op` b       bOp _ _ = throwError $ SmartyError $ T.concat [ "Tried ", d, "Op and on two non boolean values" ] -numOp :: T.Text -> (Scientific -> Scientific -> Bool) -> (Expr, Expr) -> Env -> EvalM A.Value+numOp :: Monad m => T.Text -> (Scientific -> Scientific -> Bool) -> (Expr, Expr) -> Env -> EvalM m A.Value numOp =     numGenOp boolResOp -calcOp :: T.Text -> (Scientific -> Scientific -> Scientific) -> (Expr, Expr) -> Env -> EvalM A.Value+calcOp :: Monad m => T.Text -> (Scientific -> Scientific -> Scientific) -> (Expr, Expr) -> Env -> EvalM m A.Value calcOp =     numGenOp numResOp -numGenOp :: ((A.Value -> A.Value -> EvalM a)-                 -> (Expr, Expr) -> Env -> EvalM A.Value)-         -> T.Text -> (Scientific -> Scientific -> a) -> (Expr, Expr) -> Env -> EvalM A.Value+numGenOp :: Monad m => ((A.Value -> A.Value -> EvalM m a)+                 -> (Expr, Expr) -> Env -> EvalM m A.Value)+         -> T.Text -> (Scientific -> Scientific -> a) -> (Expr, Expr) -> Env -> EvalM m A.Value numGenOp fun d op exprs env =     fun nOp exprs env     where@@ -418,15 +488,15 @@           return $ a `op` b       nOp _ _ = throwError $ SmartyError $ T.concat [ "Tried ", d, "Op and on two non numeric values" ] -numResOp :: (A.Value -> A.Value -> EvalM Scientific)-       -> (Expr, Expr) -> Env -> EvalM A.Value+numResOp :: Monad m => (A.Value -> A.Value -> EvalM m Scientific)+       -> (Expr, Expr) -> Env -> EvalM m A.Value numResOp fun (a, b) env =     do a' <- evalExpr env a        b' <- evalExpr env b        A.Number <$> fun a' b' -boolResOp :: (A.Value -> A.Value -> EvalM Bool)-       -> (Expr, Expr) -> Env -> EvalM A.Value+boolResOp :: Monad m => (A.Value -> A.Value -> EvalM m Bool)+       -> (Expr, Expr) -> Env -> EvalM m A.Value boolResOp fun (a, b) env =     do a' <- evalExpr env a        b' <- evalExpr env b
src/Text/HSmarty/Types.hs view
@@ -1,9 +1,10 @@ {-# OPTIONS_GHC -fwarn-unused-imports -fwarn-incomplete-patterns #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StrictData #-} module Text.HSmarty.Types where -import qualified Data.Text as T import qualified Data.Aeson as A+import qualified Data.Text as T  data Smarty    = Smarty@@ -18,7 +19,11 @@    | SmartyComment T.Text    | SmartyIf If    | SmartyForeach Foreach+   | SmartyCapture Capture+   | SmartyLet Let    | SmartyPrint Expr [ PrintDirective ]+   | SmartyScope Scope+   | SmartyFun FunctionDef    deriving (Eq, Show)  data Expr@@ -58,6 +63,30 @@    | BinMul Expr Expr    | BinDiv Expr Expr    deriving (Eq, Show)++data Let+    = Let+    { l_name :: T.Text+    , l_expr :: Expr+    } deriving (Show, Eq)++data Scope+    = Scope+    { s_stmts :: [SmartyStmt]+    } deriving (Show, Eq)++data Capture+    = Capture+    { c_name :: T.Text+    , c_stmts :: [SmartyStmt]+    } deriving (Show, Eq)++data FunctionDef+    = FunctionDef+    { fd_name :: T.Text+    , fd_defArgs :: [(T.Text, Expr)]+    , fd_body :: [SmartyStmt]+    } deriving (Show, Eq)  data If    = If
test/Text/HSmarty/Parser/SmartyTest.hs view
@@ -45,7 +45,23 @@  test_varParser :: IO () test_varParser =-    parserTest pVar "$hallo.sub@prop" (Variable "hallo" ["sub"] Nothing (Just "prop"))+    do parserTest pVar "$hallo.sub@prop" (Variable "hallo" ["sub"] Nothing (Just "prop"))+       parserTest pVar "$hallo.foo_bar" (Variable "hallo" ["foo_bar"] Nothing Nothing)++test_if :: IO ()+test_if =+    do parserTest pIf "{if $var@last}Bye{/if}" expect+       parserTest pIf "{if ($var@last)}Bye{/if}" expect+    where+      expect =+          If+          { if_cases =+                  [( ExprVar (Variable "var" [] Nothing (Just "last"))+                   , [SmartyText "Bye"]+                   )+                  ]+          , if_else = Nothing+          }  test_rootParser :: IO () test_rootParser =