------------------------------------------------------------------------------ -- | The Heist snaplet makes it easy to add Heist to your application and use -- it in other snaplets. -- module Snap.Snaplet.Heist ( -- * Heist and its type class Heist , HasHeist(..) -- * Initializer Functions -- $initializerSection , heistInit , heistInit' , addTemplates , addTemplatesAt , modifyHeistTS , withHeistTS , addSplices -- * Handler Functions -- $handlerSection , render , renderAs , heistServe , heistServeSingle , heistLocal , withSplices , renderWithSplices -- * Writing Splices -- $spliceSection , Unclassed.SnapletHeist , Unclassed.SnapletSplice , Unclassed.liftHeist , Unclassed.liftHandler , Unclassed.liftAppHandler , Unclassed.liftWith , Unclassed.bindSnapletSplices , clearHeistCache ) where ------------------------------------------------------------------------------ import Prelude hiding (id, (.)) import Data.ByteString (ByteString) import Data.Lens.Lazy import Data.Text (Text) import Text.Templating.Heist ------------------------------------------------------------------------------ import Snap.Snaplet import qualified Snap.Snaplet.HeistNoClass as Unclassed import Snap.Snaplet.HeistNoClass ( Heist , heistInit , heistInit' , clearHeistCache ) ------------------------------------------------------------------------------ -- | A single snaplet should never need more than one instance of Heist as a -- subsnaplet. This type class allows you to make it easy for other snaplets -- to get the lens that identifies the heist snaplet. Here's an example of -- how the heist snaplet might be declared: -- -- > data App = App { _heist :: Snaplet (Heist App) } -- > mkLabels [''App] -- > -- > instance HasHeist App where heistLens = subSnaplet heist -- > -- > appInit = makeSnaplet "app" "" Nothing $ do -- > h <- nestSnaplet "heist" heist $ heistInit "templates" -- > addSplices myAppSplices -- > return $ App h class HasHeist b where -- | A lens to the Heist snaplet. The b parameter to Heist will -- typically be the base state of your application. heistLens :: Lens (Snaplet b) (Snaplet (Heist b)) -- $initializerSection -- This section contains functions for use in setting up your Heist state -- during initialization. ------------------------------------------------------------------------------ -- | Adds templates to the Heist HeistState. Other snaplets should use -- this function to add their own templates. The templates are automatically -- read from the templates directory in the current snaplet's filesystem root. addTemplates :: HasHeist b => ByteString -- ^ The url prefix for the template routes -> Initializer b v () addTemplates pfx = withTop' heistLens (Unclassed.addTemplates pfx) ------------------------------------------------------------------------------ -- | Adds templates to the Heist HeistState, and lets you specify where -- they are found in the filesystem. Note that the path to the template -- directory is an absolute path. This allows you more flexibility in where -- your templates are located, but means that you have to explicitly call -- getSnapletFilePath if you want your snaplet to use templates within its -- normal directory structure. addTemplatesAt :: HasHeist b => ByteString -- ^ URL prefix for template routes -> FilePath -- ^ Path to templates -> Initializer b v () addTemplatesAt pfx p = withTop' heistLens (Unclassed.addTemplatesAt pfx p) ------------------------------------------------------------------------------ -- | Allows snaplets to add splices. addSplices :: (HasHeist b) => [(Text, Unclassed.SnapletSplice b v)] -- ^ Splices to bind -> Initializer b v () addSplices = Unclassed.addSplices' heistLens ------------------------------------------------------------------------------ -- | More general function allowing arbitrary HeistState modification. -- Without this function you wouldn't be able to bind more complicated splices -- like the cache tag. modifyHeistTS :: (HasHeist b) => (HeistState (Handler b b) -> HeistState (Handler b b)) -- ^ HeistState modifying function -> Initializer b v () modifyHeistTS = Unclassed.modifyHeistTS' heistLens ------------------------------------------------------------------------------ -- | Runs a function on with the Heist snaplet's 'HeistState'. withHeistTS :: (HasHeist b) => (HeistState (Handler b b) -> a) -- ^ HeistState function to run -> Handler b v a withHeistTS = Unclassed.withHeistTS' heistLens -- $handlerSection -- This section contains functions in the 'Handler' monad that you'll use in -- processing requests. ------------------------------------------------------------------------------ -- | Renders a template as text\/html. If the given template is not found, -- this returns 'empty'. render :: HasHeist b => ByteString -- ^ Template name -> Handler b v () render t = withTop' heistLens (Unclassed.render t) ------------------------------------------------------------------------------ -- | Renders a template as the given content type. If the given template -- is not found, this returns 'empty'. renderAs :: HasHeist b => ByteString -- ^ Content type to render with -> ByteString -- ^ Template name -> Handler b v () renderAs ct t = withTop' heistLens (Unclassed.renderAs ct t) ------------------------------------------------------------------------------ -- | Analogous to 'fileServe'. If the template specified in the request path -- is not found, it returns 'empty'. heistServe :: HasHeist b => Handler b v () heistServe = withTop' heistLens Unclassed.heistServe ------------------------------------------------------------------------------ -- | Analogous to 'fileServeSingle'. If the given template is not found, -- this throws an error. heistServeSingle :: HasHeist b => ByteString -- ^ Template name -> Handler b v () heistServeSingle t = withTop' heistLens (Unclassed.heistServeSingle t) ------------------------------------------------------------------------------ -- | Renders a template with a given set of splices. This is syntax sugar for -- a common combination of heistLocal, bindSplices, and render. renderWithSplices :: HasHeist b => ByteString -- ^ Template name -> [(Text, Unclassed.SnapletSplice b v)] -- ^ Splices to bind -> Handler b v () renderWithSplices = Unclassed.renderWithSplices' heistLens ------------------------------------------------------------------------------ -- | Runs an action with additional splices bound into the Heist -- 'HeistState'. withSplices :: HasHeist b => [(Text, Unclassed.SnapletSplice b v)] -- ^ Splices to bind -> Handler b v a -- ^ Handler to run -> Handler b v a withSplices = Unclassed.withSplices' heistLens ------------------------------------------------------------------------------ -- | Runs a handler with a modified 'HeistState'. You might want to use -- this if you had a set of splices which were customised for a specific -- action. To do that you would do: -- -- > heistLocal (bindSplices mySplices) handlerThatNeedsSplices heistLocal :: HasHeist b => (HeistState (Handler b b) -> HeistState (Handler b b)) -- ^ HeistState modifying function -> Handler b v a -- ^ Handler to run -> Handler b v a heistLocal = Unclassed.heistLocal' heistLens -- $spliceSection -- As can be seen in the type signature of heistLocal, the internal -- HeistState used by the heist snaplet is parameterized by (Handler b b). -- The reasons for this are beyond the scope of this discussion, but the -- result is that 'lift' inside a splice only works with @Handler b b@ -- actions. When you're writing your own snaplets you obviously would rather -- work with @Handler b v@ so your local snaplet's state is available. We -- provide the SnapletHeist monad to make this possible. The general rule is -- that when you're using Snaplets and Heist, use SnapletHeist instead of -- HeistT (previously called TemplateMonad) and use SnapletSplice instead of -- Splice.