module Web.Fn.Extra.Heist (
HeistContext(..)
, FnHeistState
, FnSplice
, FnCSplice
, heistInit
, heistServe
, render
, renderWithSplices
, cHeistServe
, cRender
, tag
, tag'
, FromAttribute(..)
, attr
, attrOpt
, (&=)
) where
import Blaze.ByteString.Builder
import Control.Applicative ((<$>), (<*>))
import Control.Arrow (first)
import Control.Lens
import Control.Monad.State
import Control.Monad.Trans.Either
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Text.Read (decimal, double)
import Heist
import qualified Heist.Compiled as C
import qualified Heist.Interpreted as I
import Network.HTTP.Types
import Network.Wai
import qualified Network.Wai.Util as W
import qualified Text.XmlHtml as X
import Web.Fn
type FnHeistState ctxt = HeistState (StateT ctxt IO)
type FnSplice ctxt = I.Splice (StateT ctxt IO)
type FnCSplice ctxt = C.Splice (StateT ctxt IO)
class HeistContext ctxt where
getHeist :: ctxt -> FnHeistState ctxt
heistInit :: HeistContext ctxt =>
[Text] ->
Splices (FnSplice ctxt) ->
Splices (FnCSplice ctxt) ->
IO (Either [String] (FnHeistState ctxt))
heistInit templateLocations isplices csplices =
do let ts = map (loadTemplates . T.unpack) templateLocations
runEitherT $ initHeist (emptyHeistConfig & hcTemplateLocations .~ ts
& hcInterpretedSplices .~ isplices
& hcLoadTimeSplices .~ defaultLoadTimeSplices
& hcCompiledSplices .~ csplices
& hcNamespace .~ "")
heistServe :: (RequestContext ctxt, HeistContext ctxt) =>
ctxt ->
IO (Maybe Response)
heistServe ctxt =
let p = pathInfo . fst $ getRequest ctxt in
mplus <$> render ctxt (T.intercalate "/" p)
<*> render ctxt (T.intercalate "/" (p ++ ["index"]))
render :: HeistContext ctxt =>
ctxt ->
Text ->
IO (Maybe Response)
render ctxt name = renderWithSplices ctxt name mempty
renderWithSplices :: HeistContext ctxt =>
ctxt ->
Text ->
Splices (FnSplice ctxt) ->
IO (Maybe Response)
renderWithSplices ctxt name splices =
do (r,_) <- runStateT (I.renderTemplate (I.bindSplices splices (getHeist ctxt)) (T.encodeUtf8 name)) ctxt
case first toLazyByteString <$> r of
Nothing -> return Nothing
Just (h,m) -> Just <$> W.bytestring status200 [(hContentType, m)] h
cRender :: HeistContext ctxt => ctxt -> Text -> IO (Maybe Response)
cRender ctxt tmpl =
let mr = C.renderTemplate (getHeist ctxt) (T.encodeUtf8 tmpl) in
case mr of
Nothing -> return Nothing
Just (rc, ct) ->
do (builder, _) <- runStateT rc ctxt
return $ Just $ responseBuilder status200 [(hContentType, ct)] builder
cHeistServe :: (RequestContext ctxt, HeistContext ctxt) =>
ctxt ->
IO (Maybe Response)
cHeistServe ctxt =
do let p = pathInfo . fst $ getRequest ctxt
mplus <$> cRender ctxt (T.intercalate "/" p)
<*> cRender ctxt (T.intercalate "/" (p ++ ["index"]))
class FromAttribute a where
fromAttribute :: Text -> Maybe a
instance FromAttribute Text where
fromAttribute = Just
instance FromAttribute Int where
fromAttribute t = case decimal t of
Left _ -> Nothing
Right m | snd m /= "" ->
Nothing
Right (v, _) -> Just v
instance FromAttribute Double where
fromAttribute t = case double t of
Left _ -> Nothing
Right m | snd m /= "" ->
Nothing
Right (v, _) -> Just v
tag :: Text ->
(X.Node -> k -> Maybe (X.Node, FnSplice ctxt)) ->
(ctxt -> X.Node -> k) ->
Splices (FnSplice ctxt)
tag name match handle =
name ## do ctxt <- lift get
node <- getParamNode
case match node (handle ctxt node) of
Nothing -> do tellSpliceError $
"Invalid attributes for splice '" <>
name <> "'"
return []
Just (_, a) -> a
tag' :: Text ->
(ctxt -> X.Node -> FnSplice ctxt) ->
Splices (FnSplice ctxt)
tag' name handle =
name ## do ctxt <- lift get
node <- getParamNode
handle ctxt node
(&=) :: (X.Node -> k -> Maybe (X.Node, k')) ->
(X.Node -> k' -> Maybe (X.Node, a)) ->
X.Node ->
k -> Maybe (X.Node, a)
(&=) a1 a2 node k =
case a1 node k of
Nothing -> Nothing
Just (_, k') -> a2 node k'
attr :: FromAttribute a =>
Text ->
X.Node ->
(a -> t) ->
Maybe (X.Node, t)
attr name node k = case X.getAttribute name node >>= fromAttribute of
Nothing -> Nothing
Just a -> Just (node, k a)
attrOpt :: FromAttribute a =>
Text ->
X.Node ->
(Maybe a -> t) ->
Maybe (X.Node, t)
attrOpt name node k =
Just (node, k (X.getAttribute name node >>= fromAttribute))