{-| Module : $Header$ Description : Types and conversions Copyright : (c) Justus Adam, 2015 License : BSD3 Maintainer : dev@justus.science Stability : experimental Portability : POSIX -} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} module Text.Mustache.Types ( -- * Types for the Parser / Template ASTree , STree , Node(..) , DataIdentifier(..) , Template(..) , TemplateCache -- * Types for the Substitution / Data , Value(..) , Key -- ** Converting , object , (~>), (↝), (~=), (⥱) , ToMustache, toMustache, mFromJSON -- ** Representation , Array, Object, Pair , Context(..) ) where import qualified Data.Aeson as Aeson import Data.Foldable (toList) import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS import qualified Data.Map as Map import Data.Scientific import qualified Data.Sequence as Seq import qualified Data.Set as Set import Data.Text import qualified Data.Text.Lazy as LT import qualified Data.Vector as V import Language.Haskell.TH.Lift (Lift (lift), deriveLift) -- | Syntax tree for a mustache template type STree = ASTree Text type ASTree α = [Node α] -- | Basic values composing the STree data Node α = TextBlock α | Section DataIdentifier (ASTree α) | InvertedSection DataIdentifier (ASTree α) | Variable Bool DataIdentifier | Partial (Maybe α) FilePath deriving (Show, Eq) -- | Kinds of identifiers for Variables and sections data DataIdentifier = NamedData [Key] | Implicit deriving (Show, Eq) -- | A list-like structure used in 'Value' type Array = V.Vector Value -- | A map-like structure used in 'Value' type Object = HM.HashMap Text Value -- | Source type for constructing 'Object's type Pair = (Text, Value) -- | Representation of stateful context for the substitution process data Context α = Context [α] α deriving (Eq, Show, Ord) -- | Internal value representation data Value = Object !Object | Array !Array | Number !Scientific | String !Text | Lambda (Context Value -> STree -> STree) | Bool !Bool | Null instance Show Value where show (Lambda _) = "Lambda function" show (Object o) = show o show (Array a) = show a show (String s) = show s show (Number n) = show n show (Bool b) = show b show Null = "null" listToMustache' :: ToMustache ω => [ω] -> Value listToMustache' = Array . V.fromList . fmap toMustache -- | Conversion class class ToMustache ω where toMustache :: ω -> Value listToMustache :: [ω] -> Value listToMustache = listToMustache' instance ToMustache Float where toMustache = Number . fromFloatDigits instance ToMustache Double where toMustache = Number . fromFloatDigits instance ToMustache Integer where toMustache = Number . fromInteger instance ToMustache Int where toMustache = toMustache . toInteger instance ToMustache Char where toMustache = toMustache . (:[]) listToMustache = String . pack instance ToMustache Value where toMustache = id instance ToMustache Bool where toMustache = Bool instance ToMustache () where toMustache = const Null instance ToMustache ω => ToMustache (Maybe ω) where toMustache (Just w) = toMustache w toMustache Nothing = Null instance ToMustache Text where toMustache = String instance ToMustache LT.Text where toMustache = String . LT.toStrict instance ToMustache Scientific where toMustache = Number instance ToMustache α => ToMustache [α] where toMustache = listToMustache instance ToMustache ω => ToMustache (Seq.Seq ω) where toMustache = listToMustache' . toList instance ToMustache ω => ToMustache (V.Vector ω) where toMustache = Array . fmap toMustache instance (ToMustache ω) => ToMustache (Map.Map Text ω) where toMustache = mapInstanceHelper id instance (ToMustache ω) => ToMustache (Map.Map LT.Text ω) where toMustache = mapInstanceHelper LT.toStrict instance (ToMustache ω) => ToMustache (Map.Map String ω) where toMustache = mapInstanceHelper pack mapInstanceHelper :: ToMustache v => (a -> Text) -> Map.Map a v -> Value mapInstanceHelper conv = toMustache . Map.foldrWithKey (\k -> HM.insert (conv k) . toMustache) HM.empty instance ToMustache ω => ToMustache (HM.HashMap Text ω) where toMustache = Object . fmap toMustache instance ToMustache ω => ToMustache (HM.HashMap LT.Text ω) where toMustache = hashMapInstanceHelper LT.toStrict instance ToMustache ω => ToMustache (HM.HashMap String ω) where toMustache = hashMapInstanceHelper pack hashMapInstanceHelper :: ToMustache v => (a -> Text) -> HM.HashMap a v -> Value hashMapInstanceHelper conv = toMustache . HM.foldrWithKey (\k -> HM.insert (conv k) . toMustache) HM.empty instance ToMustache (Context Value -> STree -> STree) where toMustache = Lambda instance ToMustache (Context Value -> STree -> Text) where toMustache = lambdaInstanceHelper id instance ToMustache (Context Value -> STree -> LT.Text) where toMustache = lambdaInstanceHelper LT.toStrict instance ToMustache (Context Value -> STree -> String) where toMustache = lambdaInstanceHelper pack lambdaInstanceHelper :: (a -> Text) -> (Context Value -> STree -> a) -> Value lambdaInstanceHelper conv f = Lambda wrapper where wrapper :: Context Value -> STree -> STree wrapper c lSTree = return . TextBlock $ conv $ f c lSTree instance ToMustache (STree -> STree) where toMustache f = toMustache (const f :: Context Value -> STree -> STree) instance ToMustache (STree -> Text) where toMustache f = toMustache wrapper where wrapper :: Context Value -> STree -> STree wrapper _ = (return . TextBlock) . f instance ToMustache Aeson.Value where toMustache (Aeson.Object o) = Object $ fmap toMustache o toMustache (Aeson.Array a) = Array $ fmap toMustache a toMustache (Aeson.Number n) = Number n toMustache (Aeson.String s) = String s toMustache (Aeson.Bool b) = Bool b toMustache Aeson.Null = Null instance ToMustache ω => ToMustache (HS.HashSet ω) where toMustache = listToMustache' . HS.toList instance ToMustache ω => ToMustache (Set.Set ω) where toMustache = listToMustache' . Set.toList instance (ToMustache α, ToMustache β) => ToMustache (α, β) where toMustache (a, b) = toMustache [toMustache a, toMustache b] instance (ToMustache α, ToMustache β, ToMustache γ) => ToMustache (α, β, γ) where toMustache (a, b, c) = toMustache [toMustache a, toMustache b, toMustache c] instance (ToMustache α, ToMustache β, ToMustache γ, ToMustache δ) => ToMustache (α, β, γ, δ) where toMustache (a, b, c, d) = toMustache [ toMustache a , toMustache b , toMustache c , toMustache d ] instance ( ToMustache α , ToMustache β , ToMustache γ , ToMustache δ , ToMustache ε ) => ToMustache (α, β, γ, δ, ε) where toMustache (a, b, c, d, e) = toMustache [ toMustache a , toMustache b , toMustache c , toMustache d , toMustache e ] instance ( ToMustache α , ToMustache β , ToMustache γ , ToMustache δ , ToMustache ε , ToMustache ζ ) => ToMustache (α, β, γ, δ, ε, ζ) where toMustache (a, b, c, d, e, f) = toMustache [ toMustache a , toMustache b , toMustache c , toMustache d , toMustache e , toMustache f ] instance ( ToMustache α , ToMustache β , ToMustache γ , ToMustache δ , ToMustache ε , ToMustache ζ , ToMustache η ) => ToMustache (α, β, γ, δ, ε, ζ, η) where toMustache (a, b, c, d, e, f, g) = toMustache [ toMustache a , toMustache b , toMustache c , toMustache d , toMustache e , toMustache f , toMustache g ] instance ( ToMustache α , ToMustache β , ToMustache γ , ToMustache δ , ToMustache ε , ToMustache ζ , ToMustache η , ToMustache θ ) => ToMustache (α, β, γ, δ, ε, ζ, η, θ) where toMustache (a, b, c, d, e, f, g, h) = toMustache [ toMustache a , toMustache b , toMustache c , toMustache d , toMustache e , toMustache f , toMustache g , toMustache h ] -- | Convenience function for creating Object values. -- -- This function is supposed to be used in conjuction with the '~>' and '~=' operators. -- -- ==== __Examples__ -- -- @ -- data Address = Address { ... } -- -- instance Address ToJSON where -- ... -- -- data Person = Person { name :: String, address :: Address } -- -- instance ToMustache Person where -- toMustache (Person { name, address }) = object -- [ "name" ~> name -- , "address" ~= address -- ] -- @ -- -- Here we can see that we can use the '~>' operator for values that have -- themselves a 'ToMustache' instance, or alternatively if they lack such an -- instance but provide an instance for the 'ToJSON' typeclass we can use the -- '~=' operator. object :: [Pair] -> Value object = Object . HM.fromList -- | Map keys to values that provide a 'ToMustache' instance -- -- Recommended in conjunction with the `OverloadedStrings` extension. (~>) :: ToMustache ω => Text -> ω -> Pair (~>) t = (t, ) . toMustache {-# INLINEABLE (~>) #-} infixr 8 ~> -- | Unicode version of '~>' (↝) :: ToMustache ω => Text -> ω -> Pair (↝) = (~>) {-# INLINEABLE (↝) #-} infixr 8 ↝ -- | Map keys to values that provide a 'ToJSON' instance -- -- Recommended in conjunction with the `OverloadedStrings` extension. (~=) :: Aeson.ToJSON ι => Text -> ι -> Pair (~=) t = (t ~>) . Aeson.toJSON {-# INLINEABLE (~=) #-} infixr 8 ~= -- | Unicode version of '~=' (⥱) :: Aeson.ToJSON ι => Text -> ι -> Pair (⥱) = (~=) {-# INLINEABLE (⥱) #-} infixr 8 ⥱ -- | Converts a value that can be represented as JSON to a Value. mFromJSON :: Aeson.ToJSON ι => ι -> Value mFromJSON = toMustache . Aeson.toJSON -- | A collection of templates with quick access via their hashed names type TemplateCache = HM.HashMap String Template -- | Type of key used for retrieving data from 'Value's type Key = Text {-| A compiled Template with metadata. -} data Template = Template { name :: String , ast :: STree , partials :: TemplateCache } deriving (Show) instance Lift TemplateCache where lift m = [| HM.fromList $(lift $ HM.toList m) |] instance Lift Text where lift = lift . unpack deriveLift ''DataIdentifier deriveLift ''Node deriveLift ''Template