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

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.FileLoader
        ( fileLoader
        )
import Language.Ginger.Interpret
import Language.Ginger.Interpret.DefEnv
        ( htmlEncoder
        , defVars
        , defVarsCompat
        )
import Language.Ginger.Parse
        ( POptions (..)
        , defPOptions
        , BlockTrimming (..)
        , BlockStripping (..)
        )
import qualified Language.Ginger.Parse as P
import Language.Ginger.RuntimeError (prettyRuntimeError)
import Language.Ginger.Value

import Control.Monad.Except (runExceptT, throwError)
import Control.Monad.Random (evalRandT, RandT)
import Control.Monad.Trans (MonadTrans (..))
import Data.Map.Strict (Map)
import Data.Text (Text)
import qualified Data.Text as Text
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 (RandT g 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 (RandT g m))
-> m (Either RuntimeError Encoded)
ginger TemplateLoader m
loader POptions
parserOptions JinjaDialect
dialect g
rng Encoder m
encoder Text
templateName Map Identifier (Value (RandT g m))
vars = 
  (RandT g m (Either RuntimeError Encoded)
 -> g -> m (Either RuntimeError Encoded))
-> g
-> RandT g m (Either RuntimeError Encoded)
-> m (Either RuntimeError Encoded)
forall a b c. (a -> b -> c) -> b -> a -> c
flip RandT g m (Either RuntimeError Encoded)
-> g -> m (Either RuntimeError Encoded)
forall (m :: * -> *) g a. Monad m => RandT g m a -> g -> m a
evalRandT g
rng (RandT g m (Either RuntimeError Encoded)
 -> m (Either RuntimeError Encoded))
-> RandT g m (Either RuntimeError Encoded)
-> m (Either RuntimeError Encoded)
forall a b. (a -> b) -> a -> b
$
  ExceptT RuntimeError (RandT g m) Encoded
-> RandT g m (Either RuntimeError Encoded)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT RuntimeError (RandT g m) Encoded
 -> RandT g m (Either RuntimeError Encoded))
-> ExceptT RuntimeError (RandT g m) Encoded
-> RandT g m (Either RuntimeError Encoded)
forall a b. (a -> b) -> a -> b
$ do
    templateSrc <- ExceptT RuntimeError (RandT g m) Text
-> (Text -> ExceptT RuntimeError (RandT g m) Text)
-> Maybe Text
-> ExceptT RuntimeError (RandT g m) Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
                    (RuntimeError -> ExceptT RuntimeError (RandT g m) Text
forall a. RuntimeError -> ExceptT RuntimeError (RandT g m) a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (RuntimeError -> ExceptT RuntimeError (RandT g m) Text)
-> RuntimeError -> ExceptT RuntimeError (RandT g m) Text
forall a b. (a -> b) -> a -> b
$ Text -> RuntimeError
TemplateFileNotFoundError Text
templateName)
                    Text -> ExceptT RuntimeError (RandT g m) Text
forall a. a -> ExceptT RuntimeError (RandT g m) a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
                    (Maybe Text -> ExceptT RuntimeError (RandT g m) Text)
-> ExceptT RuntimeError (RandT g m) (Maybe Text)
-> ExceptT RuntimeError (RandT g m) Text
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (RandT g m (Maybe Text)
-> ExceptT RuntimeError (RandT g 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 (RandT g m (Maybe Text)
 -> ExceptT RuntimeError (RandT g m) (Maybe Text))
-> (m (Maybe Text) -> RandT g m (Maybe Text))
-> m (Maybe Text)
-> ExceptT RuntimeError (RandT g m) (Maybe Text)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. m (Maybe Text) -> RandT g m (Maybe Text)
forall (m :: * -> *) a. Monad m => m a -> RandT g m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift) (TemplateLoader m
loader Text
templateName)
    let 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 <- either
                  (throwError . TemplateParseError templateName . Text.pack)
                  pure
                  parseResult
    let ctx = Context (RandT g m)
forall (m :: * -> *). MonadRandom m => Context m
defContext
                { contextEncode = (lift . encoder)
                , contextLoadTemplateFile = (lift . loader)
                }
        defVars' = case JinjaDialect
dialect of
                      JinjaDialect
DialectGinger2 -> Map Identifier (Value (RandT g m))
forall (m :: * -> *). MonadRandom m => Map Identifier (Value m)
defVars
                      JinjaDialect
DialectJinja2 -> Map Identifier (Value (RandT g m))
forall (m :: * -> *). MonadRandom m => Map Identifier (Value m)
defVarsCompat
        env = Env (RandT g m)
forall (m :: * -> *). Monad m => Env m
defEnv
                { envVars = defVars' <> vars
                }
    eitherExceptM $
      (runGingerT
        (evalT template >>= encode)
        ctx
        env
      )

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