{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeSynonymInstances #-} {- | Hastache templating for Scotty @ \{\-\# LANGUAGE OverloadedStrings \#\-\} module Main where import Text.Hastache import Web.Scotty.Trans as S import Web.Scotty.Hastache main :: IO () main = scottyH' 3000 $ do setTemplatesDir \"templates\" -- ^ Setting up the director with templates get \"/:word\" $ do beam <- param \"word\" setH \"action\" $ MuVariable (beam :: String) -- ^ \"action\" will be binded to the contents of \'beam\' hastache \"greet.html\" @ Given the following template: @ \Scotty, {{action}} me up!\<\/h1\> @ Upon the @GET \/beam@ the result will be: @ \Scotty, beam me up!\<\/h1\> @ -} module Web.Scotty.Hastache where import Control.Arrow ((***)) import Control.Monad.State as State import Data.IORef (newIORef, readIORef, writeIORef) import qualified Data.Map as M import Data.Maybe (fromMaybe) import Data.Monoid (mempty) import Data.Text.Lazy (Text) import Network.Wai (Application, Response) import Network.Wai.Handler.Warp (Port) import System.FilePath.Posix (()) import Text.Hastache import Text.Hastache.Context import Web.Scotty.Trans as S -- * Runners and types -- | The runner to use instead of 'scotty' scottyH :: (ScottyError e) => Port -> ScottyH e () -> IO () scottyH p s = do (runH, runActionToIO) <- mkHStateRunners defaultConfig scottyT p runH runActionToIO s -- | The runner to use instead of 'scottyOpts' scottyHOpts :: (ScottyError e) => Options -> ScottyH e () -> IO () scottyHOpts opts s = do (runH, runActionToIO) <- mkHStateRunners defaultConfig scottyOptsT opts runH runActionToIO s -- | A type synonym for @ScottyT e HState@; with custom exception types type ScottyH e = ScottyT e HState -- | A type synonym for @ScottyT e HState@; with custom exception types type ActionH e = ActionT e HState -- ** Specialized types and runners type ScottyH' = ScottyH Text type ActionH' = ActionH Text scottyH' :: Port -> ScottyH' () -> IO () scottyH' = scottyH scottyHOpts' :: Options -> ScottyH' () -> IO () scottyHOpts' = scottyHOpts -- * The DSL itself -- ** Configuration -- | Update the Hastache configuration as whole setHastacheConfig :: MuConfig IO -> ScottyH e () setHastacheConfig conf = do (_, tmap) <- lift State.get lift . State.put $ (conf, tmap) -- | Modify the Hastache configuration as whole modifyHastacheConfig :: (MuConfig IO -> MuConfig IO) -> ScottyH e () modifyHastacheConfig f = lift $ State.modify (f *** id) -- | Set the path to the directory with templates. This affects -- how /both/ 'hastache' and the @{{> template}}@ bit searches for the -- template files. setTemplatesDir :: FilePath -> ScottyH e () setTemplatesDir dir = do lift $ State.modify $ \(conf :: MuConfig IO, tmap) -> (conf { muTemplateFileDir = Just dir }, tmap) -- | Set the default extension for template files. This affects -- how /both/ 'hastache' and the @{{> template}}@ bit searches for the -- template files. setTemplateFileExt :: String -> ScottyH e () setTemplateFileExt ext = do lift $ State.modify $ \(conf :: MuConfig IO, tmap) -> (conf { muTemplateFileExt = Just ext }, tmap) -- ** Actions -- | This is a function, just like 'S.html' or 'S.text'. -- It takes a name of the template (the path is computed using the -- information about the templates dir and template files extension) -- and renders it using Hastache. -- -- The variables that have been initialized using 'setH' are -- substituted for their values, uninitialized variables are -- considered to be empty/null. hastache :: ScottyError e => FilePath -> ActionH e () hastache tpl = do ((conf :: MuConfig IO), tmap) <- lift State.get let cntx a = fromMaybe MuNothing (M.lookup a tmap) let tplFile = fromMaybe "." (muTemplateFileDir conf) tpl ++ fromMaybe "" (muTemplateFileExt conf) res <- liftIO $ hastacheFile conf tplFile (mkStrContext cntx) html res -- | Set the value of a mustache variable. setH :: ScottyError e => String -> MuType IO -> ActionH e () setH x y = do (conf, tmap) <- lift State.get lift . State.put $ (conf, M.insert x y tmap) -- * Internals -- | State with the Hastache config type HState = StateT (MuConfig IO, M.Map String (MuType IO)) IO mkHStateRunners :: MuConfig IO -> IO (forall a. HState a -> IO a, HState Response -> IO Response) mkHStateRunners conf = do gstate <- newIORef undefined let runH m = do (r,(muconf,_)) <- runStateT m (conf, mempty) writeIORef gstate muconf return r runActionToIO m = do muconf <- readIORef gstate evalStateT m (muconf, mempty) return (runH, runActionToIO) scottyHApp :: MuConfig IO -> ScottyH e () -> IO Application scottyHApp conf defs = do (runH, runActionToIO) <- mkHStateRunners conf scottyAppT runH runActionToIO defs