This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "snap".
The branch, master has been updated
via 2631a7e9288d6113e544b7f30ef81d4301c57ca5 (commit)
from f900096fc6e52a57001bac7332383b52be1ff6e9 (commit)
Summary of changes:
src/Snap/Extension.hs | 250 +++++++++++++++++++++++++++++++++++++-
src/Snap/Extension/Heist.hs | 18 ++-
src/Snap/Extension/Heist/Impl.hs | 22 +++-
3 files changed, 280 insertions(+), 10 deletions(-)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 2631a7e9288d6113e544b7f30ef81d4301c57ca5
Author: Ozgun Ataman <[email protected]>
Date: Mon Dec 13 02:15:50 2010 -0500
Improved haddock documentation for Snap.Extension
diff --git a/src/Snap/Extension.hs b/src/Snap/Extension.hs
index 3d19fdf..566df2e 100644
--- a/src/Snap/Extension.hs
+++ b/src/Snap/Extension.hs
@@ -1,8 +1,27 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
+
module Snap.Extension
- ( SnapExtend
+ ( -- * Introduction
+ -- $introduction
+
+ -- ** Using Snap Extensions
+ -- $using
+
+ -- *** Define Application State and Monad
+ -- $definingtypes
+
+ -- *** Provide Instances For \"HasState\" Classes
+ -- $hasstateclasses
+
+ -- *** Define The Initializer
+ -- $initializer
+
+ -- *** Simplified Snap Extension Server
+ -- $httpserve
+
+ SnapExtend
, Initializer
, InitializerState(..)
, runInitializer
@@ -29,6 +48,235 @@ import Snap.Types
import System.IO
+{- $introduction
+
+ Snap Extensions is a library which makes it easy to create reusable plugins
+ that extend your Snap application with modular chunks of functionality such
+ as session management, user authentication, templating and database
+ connection pooling.
+
+ We achieve this by requiring that you create a datatype that holds an
+ environment for your application and wrap it around the Snap monad. This new
+ construct becomes your application's handler monad and gives you access to
+ your application state throughout your handlers.
+
+-}
+
+{- $using
+
+ Every extension has an interface and at least one implementation of that
+ interface.
+
+ For some extensions, like Heist, there is only ever going to be one
+ implementation of the interface. In these cases, both the interface and the
+ implementation are exported from the same module, Snap.Extension.Heist.Impl.
+
+ For something like session management though, there could be multiple
+ implementations, one using a HDBC backend, one using a MongoDB backend and
+ one just using an encrypted cookie as backend. In these cases, the interface
+ is exported from Snap.Extension.Session, and the implementations live in
+ Snap.Extension.Session.HDBC, Snap.Extension.Session.MongoDB and
+ Snap.Extension.Session.CookieStore.
+
+ Keeping this in mind, there are a number of things you need to do to use Snap
+ extensions in your application. Let's walk through how to set up a simple
+ application with the Heist extension turned on.
+
+-}
+
+{- $definingtypes
+
+ First, we define a record type AppState for holding our application's state,
+ including the state needed by the extensions we're using.
+
+ At the same time, we also define the monad for our application, App, as
+ a type alias to @SnapExtend appst...@. 'SnapExtend' is a 'MonadSnap' and
+ a 'MonadReader', whose environment is a given type; in our case, AppState.
+
+ @
+module App where
+
+import Database.HDBC
+import Database.HDBC.ODBC
+import Snap.Extension
+import Snap.Extension.Heist
+import Snap.Types
+
+type App = SnapExtend AppState
+
+data AppState = AppState
+ { heistState :: HeistState App }
+ @
+
+ An important thing to note is that the -State types that we use in the fields
+ of AppState are specific to each implementation of a extension's interface.
+ That is, Snap.Extension.Session.HDBC will export a different SessionState to
+ Snap.Extension.Session.CookieStore, whose internal representation might be
+ completely different.
+
+ This state is what the extension's implementation needs to be able to do its
+ job.
+
+-}
+
+{- $hasstateclasses
+
+ Now we have a datatype that contains all the internal state needed by our
+ application and the extensions it uses. That's a great start! But when do we
+ actually get to use this interface and all the functionality that these
+ extensions export? What is actually being extended?
+
+ We use the interface provided by an extension inside our application's monad,
+ App. Snap extensions extend our App with new functionality by allowing us to
+ user their exported functions inside of our handlers. For example, the Heist
+ extension provides the function:
+
+ @render :: MonadHeist m => ByteString -> m ()@ that renders a template by its
+ name.
+
+ Is App a 'MonadHeist'? Well, not quite yet. Any 'MonadReader' which is also
+ a 'MonadSnap' whose environment contains a 'HeistState' is a 'MonadHeist'.
+ That sounds a lot like our App, doesn't it? We just have to tell the Heist
+ extension how to find the 'HeistState' in our AppState:
+
+ @
+instance HasHeistState AppState where
+ getHeistState = heistState
+ setHeistState hs as = as { heistState = hs }
+ @
+
+ Stated another way, if we give our AppState the ability to hold a HeistState
+ and let the HasHeistState typeclass know how to get/set this state, we are
+ /automagically/ given the ability to render heist templates in our handlers.
+
+ With these instances, our application's monad App is now a MonadHeist
+ giving it access to operations like:
+
+ @render :: MonadHeist m => ByteString -> m ()@
+
+ and
+
+ @heistLocal :: (TemplateState n -> TemplateState n) -> m a -> m a@
+
+-}
+
+{- $initializer
+
+ So, our monad is now a 'MonadHeist', but how do we actually construct our
+ AppState and turn an App () into a 'Snap' ()? We need to do this upfront,
+ once and right before our web server starts listening for connections.
+
+ Snap extensions have a thing called an 'Initializer' that does these things.
+ Each implementation of a Snap extension interface provides an 'Initializer'
+ for its -State type. We must construct an initializer type for our -State
+ type, AppState. An 'Initializer' monad is provided in this library to make
+ it easy to do this. For your convenience, 'Initializer' is an instance of
+ 'MonadIO'.
+
+ @
+appInitializer :: Initializer AppState
+appInitializer = do
+ hs <- heistInitializer \"resources/templates\"
+ return $ AppState hs
+ @
+
+ In addition to constructing the AppState, the Initializer monad also
+ constructs the init, destroy and reload functions for our application from
+ the init, reload and destroy functions for the extensions.
+
+ Although it won't cause a compile-time error, it is important to get the
+ order of the initializers correct as much as possible, otherwise they may be
+ reloaded and destroyed in the wrong order. The "right" order is an order
+ where every extension's dependencies are initialised before that extension.
+ For example, Snap.Extension.Session.HDBC would depend on something which
+ would extend the monad with MonadConnectionPool, i.e.,
+ Snap.Extension.ConnectionPool. If you had this configuration it would be
+ important that you put the connectionPoolInitializer before the
+ sessionInitializer in your appInitializer.
+
+ This Initializer AppState can then be passed to 'runInitializer', whose type
+ signature is:
+
+ @Bool -> Initializer s -> SnapExtend s () -> IO (Snap (), IO (), IO
[(ByteString, Maybe ByteString)])@
+
+ Essentially, this function takes an initializer action, our entire App () and
+ returns the 'Snap' action (which can be passed to 'httpServe'), a cleanup
+ action (which you run after 'httpServe') and a reload action (which you may
+ want to use in your handler for the path \"admin/reload\". The list it
+ returns is for error reporting - there is one tuple in the list for each Snap
+ extension; the first element of the tuple is the name of the Snap extension
+ and the second is a Maybe which contains Nothing if there was no error
+ reloading that extension and a Just with the ByteString containing the error
+ message if there was) and a cleanup action which you would run after
+ 'httpServe'.
+
+ The following is an example of how you might use this in main:
+
+ @
+main :: IO ()
+
+main = do
+ (snap,cleanup,reload) <- runInitializer appInitializer site
+ let site = snap
+ <|> path "admin/reload" $ defaultReloadHandler reload cleanup
+ quickHttpServe site
+ @
+
+ You'll notice we're using defaultReloadHandler. This is a function exported
+ by "Snap.Extension" with the type signature
+
+ @MonadSnap m => IO [(ByteString, Maybe ByteString)] -> m ()@
+
+ It takes the reload action returned by 'runInitializer' and returns a 'Snap'
+ action which renders a simple page showing how the reload went.
+
+-}
+
+{- $httpserve
+
+ This is, of course, a lot of avoidable boilerplate. Snap extensions framework
+ comes with another module "Snap.Extension.Server", which provides an interface
+ mimicking that of "Snap.Http.Server". Their function names clash, so if you
+ need to use both of them in the same module, use a qualified import. Using
+ this module, the example above becomes:
+
+ @
+import Snap.Extension.Server
+
+main :: IO ()
+main = quickHttpServe appRunner site
+ @
+
+ All it needs is a Initializer AppState and an App () and it is ready to go.
+ You might be wondering what happened to all the reload handler bits we
+ had before: That stuff has been absorbed into the config for the server.
+
+ One quick note: 'quickHttpServe' doesn't take a config, instead it uses the
+ defaults augmented with any options specified on the command-line. The
+ default reload handler path in this case is "admin/reload".
+
+ If you wanted to change this to nullReloadHandler, this is what you would do:
+
+ @
+import Snap.Extension.Server
+
+main :: IO ()
+main = do
+ config <- commandLineConfig emptyConfig
+ httpServe (setReloadHandler nullReloadHandler config) appRunner site
+ @
+
+ This behaves exactly as the above example apart from the reload handler.
+
+ With this, we now have a fully functional base application that makes use of
+ the Snap Extensions mechanism.
+
+ To initialize a directory with all of this setup provided as a starting
+ point, simply @cd@ into the desired location and type: @snap i...@. An
+ example \"Timer\" extension will also be included for your convenience.
+
+-}
+
------------------------------------------------------------------------------
-- | A 'SnapExtend' is a 'MonadReader' and a 'MonadSnap' whose environment is
-- the application state for a given progam. You would usually type alias
diff --git a/src/Snap/Extension/Heist.hs b/src/Snap/Extension/Heist.hs
index 9828aa9..f8ab482 100644
--- a/src/Snap/Extension/Heist.hs
+++ b/src/Snap/Extension/Heist.hs
@@ -5,16 +5,20 @@
'Snap.Extension.Heist' exports the 'MonadHeist' interface which allows you to
integrate Heist templates into your Snap application. The interface's
-operations are 'heistServe', 'heistServeSingle', 'heistLocal' and 'render'.
+operations are 'heistServe', 'heistServeSingle', 'heistLocal' and 'render'. As
+a convenience, we also provide 'renderWithSplices' that combines 'heistLocal'
+and 'render' into a single function call.
-'Snap.Extension.Heist.Heist' contains the only implementation of this
-interface and can be used to turn your application's monad into a
-'MonadHeist'.
+'Snap.Extension.Heist.Impl' contains the only implementation of this interface
+and can be used to turn your application's monad into a 'MonadHeist'.
'MonadHeist' is unusual among Snap extensions in that it's a multi-parameter
-typeclass. The last parameter is your application's monad, and the first is
-the monad you want the 'TemplateState' to use. This is usually, but not
-always, also your application's monad.
+typeclass. The last parameter is your application's monad, and the first is the
+monad you want the 'TemplateState' to use. This is usually, but not always,
+also your application's monad.
+
+This module should not be used directly. Instead, import
+"Snap.Extension.Heist.Impl" in your application.
-}
diff --git a/src/Snap/Extension/Heist/Impl.hs b/src/Snap/Extension/Heist/Impl.hs
index 77d701b..9b58c9b 100644
--- a/src/Snap/Extension/Heist/Impl.hs
+++ b/src/Snap/Extension/Heist/Impl.hs
@@ -47,10 +47,17 @@ interfaces from any other Snap Extension.
-}
module Snap.Extension.Heist.Impl
- ( HeistState
- , MonadHeist(..)
+ (
+
+ -- * Heist State Definitions
+ HeistState
, HasHeistState(..)
, heistInitializer
+
+ -- * The MonadHeist Interface
+ , MonadHeist(..)
+
+ -- * Convenience Functions
, registerSplices
) where
@@ -167,6 +174,17 @@ instance HasHeistState m s => MonadHeist m (ReaderT s m)
where
--
-- Typical use cases are dynamically generated components that are present in
-- many of your views.
+--
+-- Example Usage:
+--
+-- @
+-- appInit :: Initializer AppState
+-- appInit = do
+-- hs <- heistInitializer \"templates\"
+-- registerSplices hs $
+-- [ (\"tabs\", tabsSplice)
+-- , (\"loginLogout\", loginLogoutSplice) ]
+-- @
registerSplices
:: (MonadSnap m, MonadIO n)
=> HeistState m
-----------------------------------------------------------------------
hooks/post-receive
--
snap
_______________________________________________
Snap mailing list
[email protected]
http://mailman-mail5.webfaction.com/listinfo/snap