{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.Ginger
( -- * Interpreting Templates
  ginger
, GingerT
, Eval (..)
, RuntimeError (..)
, Context (..)
, defContext
, Env (..)
, emptyEnv
, defEnv
, defVars
, defVarsCompat
  -- * AST
, Statement (..)
, Expr (..)
, Template (..)
, Block (..)
  -- * Representing Values
, Value (..)
, Scalar (..)
, Encoded (..)
, prettyRuntimeError
, Identifier (..)
  -- * Configuration
, Encoder
, htmlEncoder
, JinjaDialect (..)
  -- * Parser and Parser Options
, POptions (..)
, defPOptions
, BlockTrimming (..)
, BlockStripping (..)
  -- * Template Loaders
, TemplateLoader
, fileLoader
)
where

import Language.Ginger.AST
import Language.Ginger.BuiltinsAutodoc
import Language.Ginger.Value
import Language.Ginger.Interpret
import Language.Ginger.RuntimeError (prettyRuntimeError)
import Language.Ginger.FileLoader
        ( fileLoader
        )
import Language.Ginger.Parse
        ( POptions (..)
        , defPOptions
        , BlockTrimming (..)
        , BlockStripping (..)
        )
import qualified Language.Ginger.Parse as P
import Language.Ginger.Interpret.DefEnv
        ( htmlEncoder
        , defVars
        , defVarsCompat
        )

import Control.Monad.Trans (MonadTrans (..))
import Control.Monad.Except (runExceptT, throwError)
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Map.Strict (Map)
import System.Random (SplitGen)

data JinjaDialect
  = DialectGinger2
  | DialectJinja2
  deriving (Int -> JinjaDialect -> ShowS
[JinjaDialect] -> ShowS
JinjaDialect -> String
(Int -> JinjaDialect -> ShowS)
-> (JinjaDialect -> String)
-> ([JinjaDialect] -> ShowS)
-> Show JinjaDialect
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> JinjaDialect -> ShowS
showsPrec :: Int -> JinjaDialect -> ShowS
$cshow :: JinjaDialect -> String
show :: JinjaDialect -> String
$cshowList :: [JinjaDialect] -> ShowS
showList :: [JinjaDialect] -> ShowS
Show, JinjaDialect -> JinjaDialect -> Bool
(JinjaDialect -> JinjaDialect -> Bool)
-> (JinjaDialect -> JinjaDialect -> Bool) -> Eq JinjaDialect
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: JinjaDialect -> JinjaDialect -> Bool
== :: JinjaDialect -> JinjaDialect -> Bool
$c/= :: JinjaDialect -> JinjaDialect -> Bool
/= :: JinjaDialect -> JinjaDialect -> Bool
Eq, Eq JinjaDialect
Eq JinjaDialect =>
(JinjaDialect -> JinjaDialect -> Ordering)
-> (JinjaDialect -> JinjaDialect -> Bool)
-> (JinjaDialect -> JinjaDialect -> Bool)
-> (JinjaDialect -> JinjaDialect -> Bool)
-> (JinjaDialect -> JinjaDialect -> Bool)
-> (JinjaDialect -> JinjaDialect -> JinjaDialect)
-> (JinjaDialect -> JinjaDialect -> JinjaDialect)
-> Ord JinjaDialect
JinjaDialect -> JinjaDialect -> Bool
JinjaDialect -> JinjaDialect -> Ordering
JinjaDialect -> JinjaDialect -> JinjaDialect
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: JinjaDialect -> JinjaDialect -> Ordering
compare :: JinjaDialect -> JinjaDialect -> Ordering
$c< :: JinjaDialect -> JinjaDialect -> Bool
< :: JinjaDialect -> JinjaDialect -> Bool
$c<= :: JinjaDialect -> JinjaDialect -> Bool
<= :: JinjaDialect -> JinjaDialect -> Bool
$c> :: JinjaDialect -> JinjaDialect -> Bool
> :: JinjaDialect -> JinjaDialect -> Bool
$c>= :: JinjaDialect -> JinjaDialect -> Bool
>= :: JinjaDialect -> JinjaDialect -> Bool
$cmax :: JinjaDialect -> JinjaDialect -> JinjaDialect
max :: JinjaDialect -> JinjaDialect -> JinjaDialect
$cmin :: JinjaDialect -> JinjaDialect -> JinjaDialect
min :: JinjaDialect -> JinjaDialect -> JinjaDialect
Ord, Int -> JinjaDialect
JinjaDialect -> Int
JinjaDialect -> [JinjaDialect]
JinjaDialect -> JinjaDialect
JinjaDialect -> JinjaDialect -> [JinjaDialect]
JinjaDialect -> JinjaDialect -> JinjaDialect -> [JinjaDialect]
(JinjaDialect -> JinjaDialect)
-> (JinjaDialect -> JinjaDialect)
-> (Int -> JinjaDialect)
-> (JinjaDialect -> Int)
-> (JinjaDialect -> [JinjaDialect])
-> (JinjaDialect -> JinjaDialect -> [JinjaDialect])
-> (JinjaDialect -> JinjaDialect -> [JinjaDialect])
-> (JinjaDialect -> JinjaDialect -> JinjaDialect -> [JinjaDialect])
-> Enum JinjaDialect
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: JinjaDialect -> JinjaDialect
succ :: JinjaDialect -> JinjaDialect
$cpred :: JinjaDialect -> JinjaDialect
pred :: JinjaDialect -> JinjaDialect
$ctoEnum :: Int -> JinjaDialect
toEnum :: Int -> JinjaDialect
$cfromEnum :: JinjaDialect -> Int
fromEnum :: JinjaDialect -> Int
$cenumFrom :: JinjaDialect -> [JinjaDialect]
enumFrom :: JinjaDialect -> [JinjaDialect]
$cenumFromThen :: JinjaDialect -> JinjaDialect -> [JinjaDialect]
enumFromThen :: JinjaDialect -> JinjaDialect -> [JinjaDialect]
$cenumFromTo :: JinjaDialect -> JinjaDialect -> [JinjaDialect]
enumFromTo :: JinjaDialect -> JinjaDialect -> [JinjaDialect]
$cenumFromThenTo :: JinjaDialect -> JinjaDialect -> JinjaDialect -> [JinjaDialect]
enumFromThenTo :: JinjaDialect -> JinjaDialect -> JinjaDialect -> [JinjaDialect]
Enum, JinjaDialect
JinjaDialect -> JinjaDialect -> Bounded JinjaDialect
forall a. a -> a -> Bounded a
$cminBound :: JinjaDialect
minBound :: JinjaDialect
$cmaxBound :: JinjaDialect
maxBound :: JinjaDialect
Bounded)

-- | One-stop function for parsing and interpreting a template.
ginger :: forall m g. (Monad m, SplitGen g)
       => TemplateLoader m
          -- ^ Template loader to use for loading the initial template and
          -- any included templates. For most use cases, 'fileLoader' should
          -- be appropriate.
       -> POptions
          -- ^ Parser options, determining parser behavior.
       -> JinjaDialect
          -- ^ Jinja dialect; currently determines which built-in globals to
          -- load into the initial namespace.
       -> g
       -> Encoder m
          -- ^ Encoder to use for automatic encoding. Use 'htmlEncoder' for
          -- HTML templates.
       -> Text
          -- ^ Name of the initial template to load. For the 'fileLoader', this
          -- should be a filename, but for other loaders, it can be whatever
          -- the loader expects.
       -> Map Identifier (Value m)
          -- ^ Variables defined in the initial namespace.
       -> m (Either RuntimeError Encoded)
ginger :: forall (m :: * -> *) g.
(Monad m, SplitGen g) =>
TemplateLoader m
-> POptions
-> JinjaDialect
-> g
-> Encoder m
-> Text
-> Map Identifier (Value m)
-> m (Either RuntimeError Encoded)
ginger TemplateLoader m
loader POptions
parserOptions JinjaDialect
dialect g
rng Encoder m
encoder Text
templateName Map Identifier (Value m)
vars = ExceptT RuntimeError m Encoded -> m (Either RuntimeError Encoded)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT RuntimeError m Encoded -> m (Either RuntimeError Encoded))
-> ExceptT RuntimeError m Encoded
-> m (Either RuntimeError Encoded)
forall a b. (a -> b) -> a -> b
$ do
  Text
templateSrc <- ExceptT RuntimeError m Text
-> (Text -> ExceptT RuntimeError m Text)
-> Maybe Text
-> ExceptT RuntimeError m Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
                  (RuntimeError -> ExceptT RuntimeError m Text
forall a. RuntimeError -> ExceptT RuntimeError m a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (RuntimeError -> ExceptT RuntimeError m Text)
-> RuntimeError -> ExceptT RuntimeError m Text
forall a b. (a -> b) -> a -> b
$ Text -> RuntimeError
TemplateFileNotFoundError Text
templateName)
                  Text -> ExceptT RuntimeError m Text
forall a. a -> ExceptT RuntimeError m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
                  (Maybe Text -> ExceptT RuntimeError m Text)
-> ExceptT RuntimeError m (Maybe Text)
-> ExceptT RuntimeError m Text
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< m (Maybe Text) -> ExceptT RuntimeError m (Maybe Text)
forall (m :: * -> *) a. Monad m => m a -> ExceptT RuntimeError m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (TemplateLoader m
loader Text
templateName)
  let parseResult :: Either String Template
parseResult = POptions -> P Template -> String -> Text -> Either String Template
forall a. POptions -> P a -> String -> Text -> Either String a
P.parseGingerWith
                      POptions
parserOptions
                      P Template
P.template
                      (Text -> String
Text.unpack Text
templateName)
                      Text
templateSrc
  Template
template <- (String -> ExceptT RuntimeError m Template)
-> (Template -> ExceptT RuntimeError m Template)
-> Either String Template
-> ExceptT RuntimeError m Template
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either
                (RuntimeError -> ExceptT RuntimeError m Template
forall a. RuntimeError -> ExceptT RuntimeError m a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (RuntimeError -> ExceptT RuntimeError m Template)
-> (String -> RuntimeError)
-> String
-> ExceptT RuntimeError m Template
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> RuntimeError
TemplateParseError Text
templateName (Text -> RuntimeError)
-> (String -> Text) -> String -> RuntimeError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
Text.pack)
                Template -> ExceptT RuntimeError m Template
forall a. a -> ExceptT RuntimeError m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
                Either String Template
parseResult
  let ctx :: Context m
ctx = Context m
forall (m :: * -> *). Monad m => Context m
defContext
              { contextEncode = encoder
              , contextLoadTemplateFile = loader
              }
      defVars' :: Map Identifier (Value m)
defVars' = case JinjaDialect
dialect of
                    JinjaDialect
DialectGinger2 -> Map Identifier (Value m)
forall (m :: * -> *). Monad m => Map Identifier (Value m)
defVars
                    JinjaDialect
DialectJinja2 -> Map Identifier (Value m)
forall (m :: * -> *). Monad m => Map Identifier (Value m)
defVarsCompat
      env :: Env m
env = Env m
forall (m :: * -> *). Monad m => Env m
defEnv
              { envVars = defVars' <> vars
              }
  m (Either RuntimeError Encoded) -> ExceptT RuntimeError m Encoded
forall (m :: * -> *) e (t :: (* -> *) -> * -> *) a.
(Monad m, MonadError e (t m), MonadTrans t) =>
m (Either e a) -> t m a
eitherExceptM (m (Either RuntimeError Encoded) -> ExceptT RuntimeError m Encoded)
-> m (Either RuntimeError Encoded)
-> ExceptT RuntimeError m Encoded
forall a b. (a -> b) -> a -> b
$ GingerT m Encoded
-> Context m -> Env m -> g -> m (Either RuntimeError Encoded)
forall (m :: * -> *) g a.
(Monad m, SplitGen g) =>
GingerT m a -> Context m -> Env m -> g -> m (Either RuntimeError a)
runGingerT
    (Template -> GingerT m (Value m)
forall (m :: * -> *). Monad m => Template -> GingerT m (Value m)
evalT Template
template GingerT m (Value m)
-> (Value m -> GingerT m Encoded) -> GingerT m Encoded
forall a b. GingerT m a -> (a -> GingerT m b) -> GingerT m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value m -> GingerT m Encoded
forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(Monad m, MonadError RuntimeError (t m), MonadTrans t,
 MonadReader (Context m) (t m)) =>
Value m -> t m Encoded
encode)
    Context m
ctx
    Env m
env
    g
rng

$(addHaddockFromFile "src/Language/Ginger.haddock")
$(builtinsAutodoc)