{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, RecordWildCards, TemplateHaskell, OverloadedStrings #-}
{- | @web-plugins@ is a very general purpose plugin system for web applications.

It provides facilities for loading multiple plugins and a single
theme. In the future, the @web-plugins-dynamic@ library will allow
plugins and themes to be loaded and unloaded at runtime.

A key aspect of @web-plugins@ is that all plugins for a particular system
have the same type signature. This is what makes it possible to load
new plugins at runtime.

This plugin system is not tied to any particular web server framework
or template engine.

There are four steps to using @web-plugins@:

 1. initialize the plugins system

 2. initialize the individual plugins

 3. set the theme

 4. route incoming requests to the correct plugin

To use @web-plugins@, you first initialize a 'Plugins' handle.

The 'Plugins' handle is heavily parameterized:

> newtype Plugins theme m hook config st = ...

[@theme@] is (not suprisingly) the type for you theme.

[@m@] is the monad that your plugin handlers will run in. (e.g., @ServerPart@)

[@hook@] is additional actions that should be called after the plugins have been initialized

[@config@] provides read-only configuration information

[@st@] provides mutable state that is shared between all plugins. (There is a separate mechanism for plugin-local state.)

The plugin system is typically started by using 'withPlugins'. Though,
if needed, you can call 'initPlugins' and 'destroyPlugins' instead.

The 'Plugin' record is used to create a plugin:

@
data Plugin url theme n hook config st = Plugin
    { pluginName           :: PluginName
    , pluginInit           :: Plugins theme n hook config st -> IO (Maybe Text)
    , pluginDepends        :: [PluginName]   -- ^ plugins which much be initialized before this one can be
    , pluginToPathSegments :: url -> Text
    , pluginPostHook       :: hook
    }
@

You will note that it has the same type parameters as 'Plugins' plus an additional 'url' parameter.

[@pluginName@] is a simple 'Text' value which should uniquely identify the plugin.

[@pluginInit@] will be called automatically when the plugin is loaded.

[@pluginDepends@] is a list of plugins which must be loaded before this plugin can be initialized.

[@pluginToPathSegments@] is the function that is used to convert the 'url' type to the URL path segments

[@pluginPostHook@] is the hook that you want called after the system has been initialized.

A 'Plugin' is initialized using the 'initPlugin' function (which calls the 'pluginInit' field among other things).

@
-- | initialize a plugin
initPlugin :: (Typeable url) =>
              Plugins theme n hook config st    -- ^ 'Plugins' handle
           -> Text                              -- ^ base URI to prepend to generated URLs
           -> Plugin url theme n hook config st -- ^ 'Plugin' to initialize
           -> IO (Maybe Text)                   -- ^ possible error message
@

A lot of the magic happens in the 'pluginInit' function in the
'Plugin' record. Let's look at a simple example. We will use the
following type aliases to parameterize the 'Plugins' and 'Plugin'
type:

@
type ExamplePlugins    = Plugins    Theme (ServerPart Response) (IO ()) () ()
type ExamplePlugin url = Plugin url Theme (ServerPart Response) (IO ()) () ()
@

Here is the initialization function for 'myPlugin':

@
myInit :: ExamplePlugins -> IO (Maybe Text)
myInit plugins =
    do (Just clckShowFn) <- getPluginRouteFn plugins (pluginName clckPlugin)
       (Just myShowFn)   <- getPluginRouteFn plugins (pluginName myPlugin)
       acid <- liftIO $ openLocalState MyState
       addCleanup plugins OnNormal  (putStrLn "myPlugin: normal shutdown"  >> createCheckpointAndClose acid)
       addCleanup plugins OnFailure (putStrLn "myPlugin: failure shutdown" >> closeAcidState acid)
       addHandler plugins (pluginName myPlugin) (myPluginHandler acid clckShowFn myShowFn)
       putStrLn "myInit completed."
       return Nothing
@

There are a few things to note here:

'getPluginRouteFn' is used to retrieve the the URL route showing
function for various plugins. In this case, the plugin needs to
generate routes for itself and also routes in the 'clckPlugin'.

Next it opens up an 'AcidState'. It then registers two different
cleanup functions. The 'OnNormal' cleanup will only be called if the
system is shutdown normally. The 'OnFailure' will be called if the
system is shutdown due to some error condition. If we wanted to
perform the same shutdown procedure regardless of termination cause,
we could use the 'Always' condition instead.

the 'addHandler' then registers the function which route requests for
this plugin:

@
addHandler :: MonadIO m =>
              Plugins theme n hook config st
            -> PluginName -- plugin name / prefix
            -> (Plugins theme n hook config st -> [Text] -> n)
            -> m ()
@

Each plugin should be registered using a unique prefix. When
the handler is called it will be passed the 'Plugins' handle and a
list of 'Text' values. In practice, the list 'Text' values is
typically the unconsumed path segments from the URL.

Setting the theme is done by calling the 'setTheme' function:

@
-- | set the current 'theme'
setTheme :: (MonadIO m) =>
            Plugins theme n hook config st
         -> Maybe theme
         -> m ()
@

Setting the theme to 'Nothing' will unload the theme but not load a new one.

Incoming requests are routed to the various plugins via the 'serve' function:

@
-- | serve requests using the 'Plugins' handle
serve :: Plugins theme n hook config st -- ^ 'Plugins' handle
      -> PluginName -- ^ name of the plugin to handle this request
      -> [Text]     -- ^ unconsume path segments to pass to handler
      -> IO (Either String n)
@

The expected usage is that you are going to have request with a url such as:

> /my/extra/path/segments

The code will treat the first path segment as the plugin to be called and pass in the remaining segments as the @[Text]@ arguments:

> serve plugins "my" ["extra","path","segments"]

the 'serve' function itself knows nothing about the web -- it is
framework agnostic. Here is a simple @main@ function that shows how to
tie everything together:

> main :: IO ()
> main =
>   withPlugins () () $ \plugins ->
>     do initPlugin plugins "" clckPlugin
>        initPlugin plugins "" myPlugin
>        setTheme plugins (Just theme)
>        hooks <- getPostHooks plugins
>        sequence_ hooks
>        simpleHTTP nullConf $ path $ \p -> do
>          ps <- fmap rqPaths askRq
>          r <- liftIO $ serve plugins p (map Text.pack ps)
>          case r of
>            (Left e) -> internalServerError $ toResponse e
>            (Right sp) -> sp

In this example, we do not use the @config@ or @st@ parameters so we just set them to @()@.

Note that we are responsible for calling the hooks after we have initialized all the plugins.

-}
module Web.Plugins.Core
     ( When(..)
     , Cleanup(..)
     , PluginName
     , PluginsState(..)
     , Plugins(..)
     , Rewrite(..)
     , RewriteIncoming
     , RewriteOutgoing
     , initPlugins
     , destroyPlugins
     , withPlugins
     , getPluginsSt
     , putPluginsSt
     , addPluginState
     , getPluginState
     , modifyPluginState'
     , modifyPluginsSt
     , addHandler
     , addCleanup
     , addPostHook
     , getPostHooks
     , addPluginRouteFn
     , getPluginRouteFn
     , getRewriteFn
     , setRewriteFn
     , setTheme
     , getTheme
     , getConfig
     , Plugin(..)
     , initPlugin
     , serve
     ) where

import Control.Applicative    ((<$>))
import Control.Exception      (bracketOnError)
import Control.Concurrent.STM (atomically)
import Control.Concurrent.STM.TVar (TVar, newTVar, readTVar, modifyTVar')
import Control.Monad.Trans    (MonadIO(liftIO))
import Data.Binary.Builder    (toLazyByteString)
import qualified Data.ByteString.Lazy as BS
import Data.Char              (ord)
import Data.Data              (Data, Typeable)
import Data.Dynamic           (Dynamic, toDyn, fromDynamic)
import qualified Data.Text    as Text
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import Data.List              (intersperse)
import Data.Map               (Map)
import qualified Data.Map     as Map
import Data.Maybe             (fromMaybe)
import Data.Monoid            ((<>), mempty, mconcat)
import Data.String            (fromString)
import Data.Text              (Text)
import qualified Data.Text    as T
import Data.Text.Encoding     (decodeUtf8)
import Data.Text.Lazy         (toStrict)
import Data.Text.Lazy.Builder (Builder, fromText, singleton, toLazyText)
import Network.HTTP.Types     (encodePathSegments)
import Numeric                (showIntAtBase)

-- | 'When' indicates when a clean up action should be run
data When
    = Always     -- ^ always run this action when 'destroyPlugins' is called
    | OnFailure  -- ^ only run this action if 'destroyPlugins' is called with a failure present
    | OnNormal   -- ^ only run this action when 'destroyPlugins' is called with a normal shutdown
      deriving (When -> When -> Bool
(When -> When -> Bool) -> (When -> When -> Bool) -> Eq When
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: When -> When -> Bool
$c/= :: When -> When -> Bool
== :: When -> When -> Bool
$c== :: When -> When -> Bool
Eq, Eq When
Eq When
-> (When -> When -> Ordering)
-> (When -> When -> Bool)
-> (When -> When -> Bool)
-> (When -> When -> Bool)
-> (When -> When -> Bool)
-> (When -> When -> When)
-> (When -> When -> When)
-> Ord When
When -> When -> Bool
When -> When -> Ordering
When -> When -> When
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: When -> When -> When
$cmin :: When -> When -> When
max :: When -> When -> When
$cmax :: When -> When -> When
>= :: When -> When -> Bool
$c>= :: When -> When -> Bool
> :: When -> When -> Bool
$c> :: When -> When -> Bool
<= :: When -> When -> Bool
$c<= :: When -> When -> Bool
< :: When -> When -> Bool
$c< :: When -> When -> Bool
compare :: When -> When -> Ordering
$ccompare :: When -> When -> Ordering
$cp1Ord :: Eq When
Ord, Int -> When -> ShowS
[When] -> ShowS
When -> String
(Int -> When -> ShowS)
-> (When -> String) -> ([When] -> ShowS) -> Show When
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [When] -> ShowS
$cshowList :: [When] -> ShowS
show :: When -> String
$cshow :: When -> String
showsPrec :: Int -> When -> ShowS
$cshowsPrec :: Int -> When -> ShowS
Show)

isWhen :: When -> When -> Bool
isWhen :: When -> When -> Bool
isWhen When
_ When
Always = Bool
True
isWhen When
x When
y = When
x When -> When -> Bool
forall a. Eq a => a -> a -> Bool
== When
y

-- | A 'Cleanup' is an 'IO' action to run when the server shuts
-- down. The server can either shutdown normally or due to a
-- failure. The 'When' parameter indicates when an action should run.
data Cleanup = Cleanup When (IO ())

-- | The 'PluginName' should uniquely identify a plugin -- though we
-- currently have no way to enforce that.
type PluginName = Text

-- | Rewrite or Redirect
data Rewrite
  = Rewrite  -- ^ rewrite the URL internally -- does not affect the URL displayed to the user
  | Redirect (Maybe Text) -- ^ perform a 303 redirect to a different URL
  deriving (Rewrite -> Rewrite -> Bool
(Rewrite -> Rewrite -> Bool)
-> (Rewrite -> Rewrite -> Bool) -> Eq Rewrite
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Rewrite -> Rewrite -> Bool
$c/= :: Rewrite -> Rewrite -> Bool
== :: Rewrite -> Rewrite -> Bool
$c== :: Rewrite -> Rewrite -> Bool
Eq, Eq Rewrite
Eq Rewrite
-> (Rewrite -> Rewrite -> Ordering)
-> (Rewrite -> Rewrite -> Bool)
-> (Rewrite -> Rewrite -> Bool)
-> (Rewrite -> Rewrite -> Bool)
-> (Rewrite -> Rewrite -> Bool)
-> (Rewrite -> Rewrite -> Rewrite)
-> (Rewrite -> Rewrite -> Rewrite)
-> Ord Rewrite
Rewrite -> Rewrite -> Bool
Rewrite -> Rewrite -> Ordering
Rewrite -> Rewrite -> Rewrite
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Rewrite -> Rewrite -> Rewrite
$cmin :: Rewrite -> Rewrite -> Rewrite
max :: Rewrite -> Rewrite -> Rewrite
$cmax :: Rewrite -> Rewrite -> Rewrite
>= :: Rewrite -> Rewrite -> Bool
$c>= :: Rewrite -> Rewrite -> Bool
> :: Rewrite -> Rewrite -> Bool
$c> :: Rewrite -> Rewrite -> Bool
<= :: Rewrite -> Rewrite -> Bool
$c<= :: Rewrite -> Rewrite -> Bool
< :: Rewrite -> Rewrite -> Bool
$c< :: Rewrite -> Rewrite -> Bool
compare :: Rewrite -> Rewrite -> Ordering
$ccompare :: Rewrite -> Rewrite -> Ordering
$cp1Ord :: Eq Rewrite
Ord, ReadPrec [Rewrite]
ReadPrec Rewrite
Int -> ReadS Rewrite
ReadS [Rewrite]
(Int -> ReadS Rewrite)
-> ReadS [Rewrite]
-> ReadPrec Rewrite
-> ReadPrec [Rewrite]
-> Read Rewrite
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Rewrite]
$creadListPrec :: ReadPrec [Rewrite]
readPrec :: ReadPrec Rewrite
$creadPrec :: ReadPrec Rewrite
readList :: ReadS [Rewrite]
$creadList :: ReadS [Rewrite]
readsPrec :: Int -> ReadS Rewrite
$creadsPrec :: Int -> ReadS Rewrite
Read, Int -> Rewrite -> ShowS
[Rewrite] -> ShowS
Rewrite -> String
(Int -> Rewrite -> ShowS)
-> (Rewrite -> String) -> ([Rewrite] -> ShowS) -> Show Rewrite
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Rewrite] -> ShowS
$cshowList :: [Rewrite] -> ShowS
show :: Rewrite -> String
$cshow :: Rewrite -> String
showsPrec :: Int -> Rewrite -> ShowS
$cshowsPrec :: Int -> Rewrite -> ShowS
Show, Typeable Rewrite
DataType
Constr
Typeable Rewrite
-> (forall (c :: * -> *).
    (forall d b. Data d => c (d -> b) -> d -> c b)
    -> (forall g. g -> c g) -> Rewrite -> c Rewrite)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c Rewrite)
-> (Rewrite -> Constr)
-> (Rewrite -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c Rewrite))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Rewrite))
-> ((forall b. Data b => b -> b) -> Rewrite -> Rewrite)
-> (forall r r'.
    (r -> r' -> r)
    -> r -> (forall d. Data d => d -> r') -> Rewrite -> r)
-> (forall r r'.
    (r' -> r -> r)
    -> r -> (forall d. Data d => d -> r') -> Rewrite -> r)
-> (forall u. (forall d. Data d => d -> u) -> Rewrite -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> Rewrite -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> Rewrite -> m Rewrite)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Rewrite -> m Rewrite)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Rewrite -> m Rewrite)
-> Data Rewrite
Rewrite -> DataType
Rewrite -> Constr
(forall b. Data b => b -> b) -> Rewrite -> Rewrite
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Rewrite -> c Rewrite
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Rewrite
forall a.
Typeable a
-> (forall (c :: * -> *).
    (forall d b. Data d => c (d -> b) -> d -> c b)
    -> (forall g. g -> c g) -> a -> c a)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c a)
-> (a -> Constr)
-> (a -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c a))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c a))
-> ((forall b. Data b => b -> b) -> a -> a)
-> (forall r r'.
    (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall r r'.
    (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall u. (forall d. Data d => d -> u) -> a -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> a -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> Data a
forall u. Int -> (forall d. Data d => d -> u) -> Rewrite -> u
forall u. (forall d. Data d => d -> u) -> Rewrite -> [u]
forall r r'.
(r -> r' -> r)
-> r -> (forall d. Data d => d -> r') -> Rewrite -> r
forall r r'.
(r' -> r -> r)
-> r -> (forall d. Data d => d -> r') -> Rewrite -> r
forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Rewrite
forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Rewrite -> c Rewrite
forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Rewrite)
forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Rewrite)
$cRedirect :: Constr
$cRewrite :: Constr
$tRewrite :: DataType
gmapMo :: (forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
$cgmapMo :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
gmapMp :: (forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
$cgmapMp :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
gmapM :: (forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
$cgmapM :: forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Rewrite -> m Rewrite
gmapQi :: Int -> (forall d. Data d => d -> u) -> Rewrite -> u
$cgmapQi :: forall u. Int -> (forall d. Data d => d -> u) -> Rewrite -> u
gmapQ :: (forall d. Data d => d -> u) -> Rewrite -> [u]
$cgmapQ :: forall u. (forall d. Data d => d -> u) -> Rewrite -> [u]
gmapQr :: (r' -> r -> r)
-> r -> (forall d. Data d => d -> r') -> Rewrite -> r
$cgmapQr :: forall r r'.
(r' -> r -> r)
-> r -> (forall d. Data d => d -> r') -> Rewrite -> r
gmapQl :: (r -> r' -> r)
-> r -> (forall d. Data d => d -> r') -> Rewrite -> r
$cgmapQl :: forall r r'.
(r -> r' -> r)
-> r -> (forall d. Data d => d -> r') -> Rewrite -> r
gmapT :: (forall b. Data b => b -> b) -> Rewrite -> Rewrite
$cgmapT :: (forall b. Data b => b -> b) -> Rewrite -> Rewrite
dataCast2 :: (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Rewrite)
$cdataCast2 :: forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Rewrite)
dataCast1 :: (forall d. Data d => c (t d)) -> Maybe (c Rewrite)
$cdataCast1 :: forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Rewrite)
dataTypeOf :: Rewrite -> DataType
$cdataTypeOf :: Rewrite -> DataType
toConstr :: Rewrite -> Constr
$ctoConstr :: Rewrite -> Constr
gunfold :: (forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Rewrite
$cgunfold :: forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Rewrite
gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Rewrite -> c Rewrite
$cgfoldl :: forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Rewrite -> c Rewrite
$cp1Data :: Typeable Rewrite
Data, Typeable)

-- | rewrite the URL from a Request before routing it
type RewriteIncoming = IO ([Text] -> [(Text, Maybe Text)] -> Maybe (Rewrite, [Text], [(Text, Maybe Text)]))

-- | rewrite a URL that is going to end up in a HTML document or other output
type RewriteOutgoing = IO ([Text] -> [(Text, Maybe Text)] -> Maybe ([Text], [(Text, Maybe Text)]))

-- | The 'PluginsState' record holds all the record keeping
-- information needed for loading, unloading, and invoking plugins. In
-- theory you should not be modifying or inspecting this structure
-- directly -- only calling the helper functions that modify or read
-- it.
data PluginsState theme n hook config st = PluginsState
    { PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsHandler     :: Map PluginName (Plugins theme n hook config st -> [Text] -> n)
    , PluginsState theme n hook config st -> [Cleanup]
pluginsOnShutdown  :: [Cleanup]
    , PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsRouteFn     :: Map PluginName (Text, Dynamic) -- ^ baseURI, url -> [Text]
    , PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsPluginState :: Map PluginName (TVar Dynamic)  -- ^ per-plugin state
    , PluginsState theme n hook config st -> Maybe theme
pluginsTheme       :: Maybe theme
    , PluginsState theme n hook config st -> [hook]
pluginsPostHooks   :: [hook]
    , PluginsState theme n hook config st -> config
pluginsConfig      :: config
    , PluginsState theme n hook config st -> st
pluginsState       :: st
    , PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsRewrite     :: Maybe (RewriteIncoming, RewriteOutgoing) -- ^ functions rewrite the incoming and outgoing URLs
    }

-- | The 'Plugins' type is the handle to the plugins system. Generally
-- you will have exactly one 'Plugins' value in your app.
--
-- see also 'withPlugins'
newtype Plugins theme m hook config st = Plugins { Plugins theme m hook config st
-> TVar (PluginsState theme m hook config st)
ptv :: TVar (PluginsState theme m hook config st) }

-- | initialize the plugins system
--
-- see also 'withPlugins'
initPlugins :: config -- ^ initial value for the 'config' field of 'PluginsState'
            -> st     -- ^ initial value for the 'state' field of the 'PluginsState'
            -> IO (Plugins theme n hook config st)
initPlugins :: config -> st -> IO (Plugins theme n hook config st)
initPlugins config
config st
st =
    do TVar (PluginsState theme n hook config st)
ptv <- STM (TVar (PluginsState theme n hook config st))
-> IO (TVar (PluginsState theme n hook config st))
forall a. STM a -> IO a
atomically (STM (TVar (PluginsState theme n hook config st))
 -> IO (TVar (PluginsState theme n hook config st)))
-> STM (TVar (PluginsState theme n hook config st))
-> IO (TVar (PluginsState theme n hook config st))
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st
-> STM (TVar (PluginsState theme n hook config st))
forall a. a -> STM (TVar a)
newTVar
              (PluginsState :: forall theme n hook config st.
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
-> [Cleanup]
-> Map PluginName (PluginName, Dynamic)
-> Map PluginName (TVar Dynamic)
-> Maybe theme
-> [hook]
-> config
-> st
-> Maybe (RewriteIncoming, RewriteOutgoing)
-> PluginsState theme n hook config st
PluginsState { pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsHandler     = Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
forall k a. Map k a
Map.empty
                            , pluginsOnShutdown :: [Cleanup]
pluginsOnShutdown  = []
                            , pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsRouteFn     = Map PluginName (PluginName, Dynamic)
forall k a. Map k a
Map.empty
                            , pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsPluginState = Map PluginName (TVar Dynamic)
forall k a. Map k a
Map.empty
                            , pluginsTheme :: Maybe theme
pluginsTheme       = Maybe theme
forall a. Maybe a
Nothing
                            , pluginsPostHooks :: [hook]
pluginsPostHooks   = []
                            , pluginsConfig :: config
pluginsConfig      = config
config
                            , pluginsState :: st
pluginsState       = st
st
                            , pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsRewrite     = Maybe (RewriteIncoming, RewriteOutgoing)
forall a. Maybe a
Nothing
                            }
              )
       Plugins theme n hook config st
-> IO (Plugins theme n hook config st)
forall (m :: * -> *) a. Monad m => a -> m a
return (TVar (PluginsState theme n hook config st)
-> Plugins theme n hook config st
forall theme m hook config st.
TVar (PluginsState theme m hook config st)
-> Plugins theme m hook config st
Plugins TVar (PluginsState theme n hook config st)
ptv)

-- | shutdown the plugins system
--
-- see also 'withPlugins'
destroyPlugins :: When                           -- ^ should be 'OnFailure' or 'OnNormal'
               -> Plugins theme m hook config st -- ^ handle to the plugins
               -> IO ()
destroyPlugins :: When -> Plugins theme m hook config st -> IO ()
destroyPlugins When
whn (Plugins TVar (PluginsState theme m hook config st)
ptv) =
    do [Cleanup]
pos <- STM [Cleanup] -> IO [Cleanup]
forall a. STM a -> IO a
atomically (STM [Cleanup] -> IO [Cleanup]) -> STM [Cleanup] -> IO [Cleanup]
forall a b. (a -> b) -> a -> b
$ PluginsState theme m hook config st -> [Cleanup]
forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsOnShutdown (PluginsState theme m hook config st -> [Cleanup])
-> STM (PluginsState theme m hook config st) -> STM [Cleanup]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme m hook config st)
-> STM (PluginsState theme m hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme m hook config st)
ptv
       (Cleanup -> IO ()) -> [Cleanup] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (When -> Cleanup -> IO ()
cleanup When
whn) [Cleanup]
pos
       () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    where
      cleanup :: When -> Cleanup -> IO ()
cleanup When
w (Cleanup When
w' IO ()
action)
          | When -> When -> Bool
isWhen When
w When
w' = IO ()
action
          | Bool
otherwise   = () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- | a bracketed combination of 'initPlugins' and 'destroyPlugins'. Takes care of passing the correct termination condition.
withPlugins :: config -- ^ initial config value
            -> st     -- ^ initial state value
            -> (Plugins theme m hook config st -> IO a) -> IO a
withPlugins :: config -> st -> (Plugins theme m hook config st -> IO a) -> IO a
withPlugins config
config st
st Plugins theme m hook config st -> IO a
action =
    IO (Plugins theme m hook config st)
-> (Plugins theme m hook config st -> IO ())
-> (Plugins theme m hook config st -> IO a)
-> IO a
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracketOnError (config -> st -> IO (Plugins theme m hook config st)
forall config st theme n hook.
config -> st -> IO (Plugins theme n hook config st)
initPlugins config
config st
st)
                   (When -> Plugins theme m hook config st -> IO ()
forall theme m hook config st.
When -> Plugins theme m hook config st -> IO ()
destroyPlugins When
OnFailure)
                   (\Plugins theme m hook config st
p -> do a
r <- Plugins theme m hook config st -> IO a
action Plugins theme m hook config st
p ; When -> Plugins theme m hook config st -> IO ()
forall theme m hook config st.
When -> Plugins theme m hook config st -> IO ()
destroyPlugins When
OnNormal Plugins theme m hook config st
p; a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
r)

------------------------------------------------------------------------------
-- PluginsConfig
------------------------------------------------------------------------------

-- | get the current @st@ value from 'Plugins'
getPluginsConfig :: (MonadIO m) => Plugins theme n hook config st
             -> m config
getPluginsConfig :: Plugins theme n hook config st -> m config
getPluginsConfig (Plugins TVar (PluginsState theme n hook config st)
tps) =
    IO config -> m config
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO config -> m config) -> IO config -> m config
forall a b. (a -> b) -> a -> b
$ STM config -> IO config
forall a. STM a -> IO a
atomically (STM config -> IO config) -> STM config -> IO config
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st -> config
forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsConfig (PluginsState theme n hook config st -> config)
-> STM (PluginsState theme n hook config st) -> STM config
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
tps

------------------------------------------------------------------------------
-- PluginsSt
------------------------------------------------------------------------------

-- | get the current @st@ value from 'Plugins'
getPluginsSt :: (MonadIO m) => Plugins theme n hook config st
             -> m st
getPluginsSt :: Plugins theme n hook config st -> m st
getPluginsSt (Plugins TVar (PluginsState theme n hook config st)
tps) =
    IO st -> m st
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO st -> m st) -> IO st -> m st
forall a b. (a -> b) -> a -> b
$ STM st -> IO st
forall a. STM a -> IO a
atomically (STM st -> IO st) -> STM st -> IO st
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st -> st
forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsState (PluginsState theme n hook config st -> st)
-> STM (PluginsState theme n hook config st) -> STM st
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
tps

-- | put the current st value from 'Plugins'
putPluginsSt :: (MonadIO m) => Plugins theme n hook config st -> st -> m ()
putPluginsSt :: Plugins theme n hook config st -> st -> m ()
putPluginsSt (Plugins TVar (PluginsState theme n hook config st)
tps) st
st =
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
        PluginsState theme n hook config st
ps { pluginsState :: st
pluginsState = st
st }

-- | modify the current st value from 'Plugins'
modifyPluginsSt :: (MonadIO m) => Plugins theme n hook config st
                -> (st -> st)
                -> m ()
modifyPluginsSt :: Plugins theme n hook config st -> (st -> st) -> m ()
modifyPluginsSt (Plugins TVar (PluginsState theme n hook config st)
tps) st -> st
f =
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
        PluginsState theme n hook config st
ps { pluginsState :: st
pluginsState = st -> st
f st
pluginsState }

-- | add a new route handler
addHandler :: (MonadIO m) =>
              Plugins theme n hook config st
           -> PluginName -- ^ prefix which this route handles
           -> (Plugins theme n hook config st -> [Text] -> n)
           -> m ()
addHandler :: Plugins theme n hook config st
-> PluginName
-> (Plugins theme n hook config st -> [PluginName] -> n)
-> m ()
addHandler (Plugins TVar (PluginsState theme n hook config st)
tps) PluginName
pname Plugins theme n hook config st -> [PluginName] -> n
ph =
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
              PluginsState theme n hook config st
ps { pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsHandler = PluginName
-> (Plugins theme n hook config st -> [PluginName] -> n)
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert PluginName
pname Plugins theme n hook config st -> [PluginName] -> n
ph Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsHandler }

-- | add a new plugin-local state
addPluginState :: (MonadIO m, Typeable state) => Plugins theme n hook config st
               -> PluginName -- plugin name
               -> state
               -> m ()
addPluginState :: Plugins theme n hook config st -> PluginName -> state -> m ()
addPluginState (Plugins TVar (PluginsState theme n hook config st)
tps) PluginName
pname state
state =
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$
           do TVar Dynamic
stateTV <- Dynamic -> STM (TVar Dynamic)
forall a. a -> STM (TVar a)
newTVar (state -> Dynamic
forall a. Typeable a => a -> Dynamic
toDyn state
state)
              TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
                    PluginsState theme n hook config st
ps { pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsPluginState = PluginName
-> TVar Dynamic
-> Map PluginName (TVar Dynamic)
-> Map PluginName (TVar Dynamic)
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert PluginName
pname TVar Dynamic
stateTV Map PluginName (TVar Dynamic)
pluginsPluginState }

-- | Get the state for a particular plugin
--
-- per-plugin state is optional. This will return 'Nothing' if the
-- plugin did not register any local state.
getPluginState :: (MonadIO m, Typeable state) =>
                  Plugins theme n hook config st
               -> Text -- plugin name
               -> m (Maybe state)
getPluginState :: Plugins theme n hook config st -> PluginName -> m (Maybe state)
getPluginState (Plugins TVar (PluginsState theme n hook config st)
ptv) PluginName
pluginName =
    do Map PluginName (TVar Dynamic)
states <- IO (Map PluginName (TVar Dynamic))
-> m (Map PluginName (TVar Dynamic))
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Map PluginName (TVar Dynamic))
 -> m (Map PluginName (TVar Dynamic)))
-> IO (Map PluginName (TVar Dynamic))
-> m (Map PluginName (TVar Dynamic))
forall a b. (a -> b) -> a -> b
$ STM (Map PluginName (TVar Dynamic))
-> IO (Map PluginName (TVar Dynamic))
forall a. STM a -> IO a
atomically (STM (Map PluginName (TVar Dynamic))
 -> IO (Map PluginName (TVar Dynamic)))
-> STM (Map PluginName (TVar Dynamic))
-> IO (Map PluginName (TVar Dynamic))
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsPluginState (PluginsState theme n hook config st
 -> Map PluginName (TVar Dynamic))
-> STM (PluginsState theme n hook config st)
-> STM (Map PluginName (TVar Dynamic))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
ptv
       case PluginName -> Map PluginName (TVar Dynamic) -> Maybe (TVar Dynamic)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup PluginName
pluginName Map PluginName (TVar Dynamic)
states of
         Maybe (TVar Dynamic)
Nothing -> Maybe state -> m (Maybe state)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe state
forall a. Maybe a
Nothing
         (Just TVar Dynamic
tvar) ->
             do Dynamic
dyn <- IO Dynamic -> m Dynamic
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Dynamic -> m Dynamic) -> IO Dynamic -> m Dynamic
forall a b. (a -> b) -> a -> b
$ STM Dynamic -> IO Dynamic
forall a. STM a -> IO a
atomically (STM Dynamic -> IO Dynamic) -> STM Dynamic -> IO Dynamic
forall a b. (a -> b) -> a -> b
$ TVar Dynamic -> STM Dynamic
forall a. TVar a -> STM a
readTVar TVar Dynamic
tvar
                Maybe state -> m (Maybe state)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe state -> m (Maybe state)) -> Maybe state -> m (Maybe state)
forall a b. (a -> b) -> a -> b
$ Dynamic -> Maybe state
forall a. Typeable a => Dynamic -> Maybe a
fromDynamic Dynamic
dyn

-- | modify the plugin state
--
-- If the plugin did not register any state, then this is a noop
modifyPluginState' :: (MonadIO m, Typeable state) =>
                  Plugins theme n hook config st
               -> Text -- plugin name
               -> (state -> state)
               -> m ()
modifyPluginState' :: Plugins theme n hook config st
-> PluginName -> (state -> state) -> m ()
modifyPluginState'  (Plugins TVar (PluginsState theme n hook config st)
ptv) PluginName
pluginName state -> state
modifier =
    do Map PluginName (TVar Dynamic)
states <- IO (Map PluginName (TVar Dynamic))
-> m (Map PluginName (TVar Dynamic))
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Map PluginName (TVar Dynamic))
 -> m (Map PluginName (TVar Dynamic)))
-> IO (Map PluginName (TVar Dynamic))
-> m (Map PluginName (TVar Dynamic))
forall a b. (a -> b) -> a -> b
$ STM (Map PluginName (TVar Dynamic))
-> IO (Map PluginName (TVar Dynamic))
forall a. STM a -> IO a
atomically (STM (Map PluginName (TVar Dynamic))
 -> IO (Map PluginName (TVar Dynamic)))
-> STM (Map PluginName (TVar Dynamic))
-> IO (Map PluginName (TVar Dynamic))
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsPluginState (PluginsState theme n hook config st
 -> Map PluginName (TVar Dynamic))
-> STM (PluginsState theme n hook config st)
-> STM (Map PluginName (TVar Dynamic))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
ptv
       case PluginName -> Map PluginName (TVar Dynamic) -> Maybe (TVar Dynamic)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup PluginName
pluginName Map PluginName (TVar Dynamic)
states of
         Maybe (TVar Dynamic)
Nothing -> () -> m ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
         (Just TVar Dynamic
tvar) ->
             do IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar Dynamic -> (Dynamic -> Dynamic) -> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar Dynamic
tvar ((Dynamic -> Dynamic) -> STM ()) -> (Dynamic -> Dynamic) -> STM ()
forall a b. (a -> b) -> a -> b
$ \Dynamic
d ->
                  case Dynamic -> Maybe state
forall a. Typeable a => Dynamic -> Maybe a
fromDynamic Dynamic
d of
                    Maybe state
Nothing -> Dynamic
d
                    (Just state
st) -> state -> Dynamic
forall a. Typeable a => a -> Dynamic
toDyn (state -> state
modifier state
st)
                () -> m ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

-- | add a new cleanup action to the top of the stack
addCleanup :: (MonadIO m) => Plugins theme n hook config st -> When -> IO () -> m ()
addCleanup :: Plugins theme n hook config st -> When -> IO () -> m ()
addCleanup (Plugins TVar (PluginsState theme n hook config st)
tps) When
when IO ()
action =
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
        PluginsState theme n hook config st
ps { pluginsOnShutdown :: [Cleanup]
pluginsOnShutdown = (When -> IO () -> Cleanup
Cleanup When
when IO ()
action) Cleanup -> [Cleanup] -> [Cleanup]
forall a. a -> [a] -> [a]
: [Cleanup]
pluginsOnShutdown }

-- | add a new post initialization hook
addPostHook :: (MonadIO m) =>
               Plugins theme n hook config st
            -> hook
            -> m ()
addPostHook :: Plugins theme n hook config st -> hook -> m ()
addPostHook (Plugins TVar (PluginsState theme n hook config st)
tps) hook
postHook =
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
              PluginsState theme n hook config st
ps { pluginsPostHooks :: [hook]
pluginsPostHooks = hook
postHook hook -> [hook] -> [hook]
forall a. a -> [a] -> [a]
: [hook]
pluginsPostHooks }

-- | get all the post initialization hooks
getPostHooks :: (MonadIO m) =>
               Plugins theme n hook config st
            -> m [hook]
getPostHooks :: Plugins theme n hook config st -> m [hook]
getPostHooks (Plugins TVar (PluginsState theme n hook config st)
tps) =
    IO [hook] -> m [hook]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [hook] -> m [hook]) -> IO [hook] -> m [hook]
forall a b. (a -> b) -> a -> b
$ STM [hook] -> IO [hook]
forall a. STM a -> IO a
atomically (STM [hook] -> IO [hook]) -> STM [hook] -> IO [hook]
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st -> [hook]
forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsPostHooks (PluginsState theme n hook config st -> [hook])
-> STM (PluginsState theme n hook config st) -> STM [hook]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
tps

-- | add the routing function for a plugin
--
-- see also: 'getPluginRouteFn'
addPluginRouteFn :: (MonadIO m, Typeable url) =>
                    Plugins theme n hook config st
                 -> PluginName
                 -> Text -- ^ baseURI
                 -> (url -> [Text]) -- ^ url to path segments
                 -> m ()
addPluginRouteFn :: Plugins theme n hook config st
-> PluginName -> PluginName -> (url -> [PluginName]) -> m ()
addPluginRouteFn (Plugins TVar (PluginsState theme n hook config st)
tpv) PluginName
pluginName PluginName
baseURI url -> [PluginName]
routeFn =
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ do -- putStrLn $ "Adding route for " ++ Text.unpack pluginName
                STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tpv ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
                  PluginsState theme n hook config st
ps { pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsRouteFn = PluginName
-> (PluginName, Dynamic)
-> Map PluginName (PluginName, Dynamic)
-> Map PluginName (PluginName, Dynamic)
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert PluginName
pluginName (PluginName
baseURI, ((url -> [PluginName]) -> Dynamic
forall a. Typeable a => a -> Dynamic
toDyn url -> [PluginName]
routeFn)) Map PluginName (PluginName, Dynamic)
pluginsRouteFn }

-- | get the plugin routing function for the named plugin
--
-- see also: 'addPluginRouteFn'
getPluginRouteFn :: (MonadIO m, Typeable url) =>
                    Plugins theme n hook config st
                 -> PluginName -- ^ name of plugin
                 -> m (Maybe (url -> [(Text, Maybe Text)] -> Text))
getPluginRouteFn :: Plugins theme n hook config st
-> PluginName
-> m (Maybe
        (url -> [(PluginName, Maybe PluginName)] -> PluginName))
getPluginRouteFn (Plugins TVar (PluginsState theme n hook config st)
ptv) PluginName
pluginName =
    do -- liftIO $ putStrLn $ "looking up route function for " ++ Text.unpack pluginName
       PluginsState theme n hook config st
ps <- IO (PluginsState theme n hook config st)
-> m (PluginsState theme n hook config st)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (PluginsState theme n hook config st)
 -> m (PluginsState theme n hook config st))
-> IO (PluginsState theme n hook config st)
-> m (PluginsState theme n hook config st)
forall a b. (a -> b) -> a -> b
$ STM (PluginsState theme n hook config st)
-> IO (PluginsState theme n hook config st)
forall a. STM a -> IO a
atomically (STM (PluginsState theme n hook config st)
 -> IO (PluginsState theme n hook config st))
-> STM (PluginsState theme n hook config st)
-> IO (PluginsState theme n hook config st)
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
ptv
       -- check if there is a plugin with this prefix
       case PluginName
-> Map PluginName (PluginName, Dynamic)
-> Maybe (PluginName, Dynamic)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup PluginName
pluginName (PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsRouteFn PluginsState theme n hook config st
ps) of
         Maybe (PluginName, Dynamic)
Nothing -> do -- liftIO $ putStrLn "oops, route not found."
                       Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
-> m (Maybe
        (url -> [(PluginName, Maybe PluginName)] -> PluginName))
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
forall a. Maybe a
Nothing
         (Just (PluginName
baseURI, Dynamic
dyn)) ->
           -- extract the URL show function for this plugin
           case Dynamic -> Maybe (url -> [PluginName])
forall a. Typeable a => Dynamic -> Maybe a
fromDynamic Dynamic
dyn of
             Maybe (url -> [PluginName])
Nothing       -> Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
-> m (Maybe
        (url -> [(PluginName, Maybe PluginName)] -> PluginName))
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
forall a. Maybe a
Nothing
             (Just url -> [PluginName]
showFn) ->
               -- get rewrite function
               do [PluginName]
-> [(PluginName, Maybe PluginName)]
-> Maybe ([PluginName], [(PluginName, Maybe PluginName)])
f <- case PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsRewrite PluginsState theme n hook config st
ps of
                    Maybe (RewriteIncoming, RewriteOutgoing)
Nothing -> ([PluginName]
 -> [(PluginName, Maybe PluginName)]
 -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
-> m ([PluginName]
      -> [(PluginName, Maybe PluginName)]
      -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (([PluginName]
  -> [(PluginName, Maybe PluginName)]
  -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
 -> m ([PluginName]
       -> [(PluginName, Maybe PluginName)]
       -> Maybe ([PluginName], [(PluginName, Maybe PluginName)])))
-> ([PluginName]
    -> [(PluginName, Maybe PluginName)]
    -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
-> m ([PluginName]
      -> [(PluginName, Maybe PluginName)]
      -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
forall a b. (a -> b) -> a -> b
$ \[PluginName]
pathSegments [(PluginName, Maybe PluginName)]
params -> Maybe ([PluginName], [(PluginName, Maybe PluginName)])
forall a. Maybe a
Nothing
                    (Just (RewriteIncoming
_, RewriteOutgoing
outgoingFn)) ->
                      do [PluginName]
-> [(PluginName, Maybe PluginName)]
-> Maybe ([PluginName], [(PluginName, Maybe PluginName)])
f <- RewriteOutgoing
-> m ([PluginName]
      -> [(PluginName, Maybe PluginName)]
      -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO RewriteOutgoing
outgoingFn
                         ([PluginName]
 -> [(PluginName, Maybe PluginName)]
 -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
-> m ([PluginName]
      -> [(PluginName, Maybe PluginName)]
      -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (([PluginName]
  -> [(PluginName, Maybe PluginName)]
  -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
 -> m ([PluginName]
       -> [(PluginName, Maybe PluginName)]
       -> Maybe ([PluginName], [(PluginName, Maybe PluginName)])))
-> ([PluginName]
    -> [(PluginName, Maybe PluginName)]
    -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
-> m ([PluginName]
      -> [(PluginName, Maybe PluginName)]
      -> Maybe ([PluginName], [(PluginName, Maybe PluginName)]))
forall a b. (a -> b) -> a -> b
$ [PluginName]
-> [(PluginName, Maybe PluginName)]
-> Maybe ([PluginName], [(PluginName, Maybe PluginName)])
f
                  Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
-> m (Maybe
        (url -> [(PluginName, Maybe PluginName)] -> PluginName))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
 -> m (Maybe
         (url -> [(PluginName, Maybe PluginName)] -> PluginName)))
-> Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
-> m (Maybe
        (url -> [(PluginName, Maybe PluginName)] -> PluginName))
forall a b. (a -> b) -> a -> b
$ (url -> [(PluginName, Maybe PluginName)] -> PluginName)
-> Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
forall a. a -> Maybe a
Just ((url -> [(PluginName, Maybe PluginName)] -> PluginName)
 -> Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName))
-> (url -> [(PluginName, Maybe PluginName)] -> PluginName)
-> Maybe (url -> [(PluginName, Maybe PluginName)] -> PluginName)
forall a b. (a -> b) -> a -> b
$ \url
u [(PluginName, Maybe PluginName)]
p ->
                    let pathSegments :: [PluginName]
pathSegments = PluginName
pluginName PluginName -> [PluginName] -> [PluginName]
forall a. a -> [a] -> [a]
: (url -> [PluginName]
showFn url
u)
                    in let ([PluginName]
paths, [(PluginName, Maybe PluginName)]
params) =
                             case [PluginName]
-> [(PluginName, Maybe PluginName)]
-> Maybe ([PluginName], [(PluginName, Maybe PluginName)])
f [PluginName]
pathSegments [(PluginName, Maybe PluginName)]
p of
                               Maybe ([PluginName], [(PluginName, Maybe PluginName)])
Nothing -> ([PluginName]
pathSegments, [(PluginName, Maybe PluginName)]
p)
                               (Just ([PluginName]
pathSegments', [(PluginName, Maybe PluginName)]
p')) -> ([PluginName]
pathSegments', [(PluginName, Maybe PluginName)]
p')
                       in PluginName
baseURI PluginName -> PluginName -> PluginName
forall a. Semigroup a => a -> a -> a
<> (ByteString -> PluginName
decodeUtf8 (ByteString -> PluginName) -> ByteString -> PluginName
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
BS.toStrict (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Builder -> ByteString
toLazyByteString (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$ [PluginName] -> Builder
encodePathSegments [PluginName]
pathSegments)  PluginName -> PluginName -> PluginName
forall a. Semigroup a => a -> a -> a
<> [(PluginName, PluginName)] -> PluginName
paramsToQueryString (((PluginName, Maybe PluginName) -> (PluginName, PluginName))
-> [(PluginName, Maybe PluginName)] -> [(PluginName, PluginName)]
forall a b. (a -> b) -> [a] -> [b]
map (\(PluginName
k, Maybe PluginName
v) -> (PluginName
k, PluginName -> Maybe PluginName -> PluginName
forall a. a -> Maybe a -> a
fromMaybe PluginName
forall a. Monoid a => a
mempty Maybe PluginName
v)) [(PluginName, Maybe PluginName)]
params)

-- | set the current @theme@
setTheme :: (MonadIO m) =>
            Plugins theme n hook config st
         -> Maybe theme
         -> m ()
setTheme :: Plugins theme n hook config st -> Maybe theme -> m ()
setTheme (Plugins TVar (PluginsState theme n hook config st)
tps) Maybe theme
theme =
        IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
              PluginsState theme n hook config st
ps { pluginsTheme :: Maybe theme
pluginsTheme = Maybe theme
theme }

-- | get the current @theme@
getTheme :: (MonadIO m) =>
            Plugins theme n hook config st
         -> m (Maybe theme)
getTheme :: Plugins theme n hook config st -> m (Maybe theme)
getTheme (Plugins TVar (PluginsState theme n hook config st)
tvp) =
    IO (Maybe theme) -> m (Maybe theme)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Maybe theme) -> m (Maybe theme))
-> IO (Maybe theme) -> m (Maybe theme)
forall a b. (a -> b) -> a -> b
$ STM (Maybe theme) -> IO (Maybe theme)
forall a. STM a -> IO a
atomically (STM (Maybe theme) -> IO (Maybe theme))
-> STM (Maybe theme) -> IO (Maybe theme)
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st -> Maybe theme
forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsTheme (PluginsState theme n hook config st -> Maybe theme)
-> STM (PluginsState theme n hook config st) -> STM (Maybe theme)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
tvp

-- | get the @config@ value from the 'Plugins' type
getConfig :: (MonadIO m) =>
             Plugins theme n hook config st
          -> m config
getConfig :: Plugins theme n hook config st -> m config
getConfig (Plugins TVar (PluginsState theme n hook config st)
tvp) =
    IO config -> m config
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO config -> m config) -> IO config -> m config
forall a b. (a -> b) -> a -> b
$ STM config -> IO config
forall a. STM a -> IO a
atomically (STM config -> IO config) -> STM config -> IO config
forall a b. (a -> b) -> a -> b
$ PluginsState theme n hook config st -> config
forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsConfig (PluginsState theme n hook config st -> config)
-> STM (PluginsState theme n hook config st) -> STM config
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
tvp

setRewriteFn :: (MonadIO m) =>
                Plugins theme n hook config st
             -> Maybe (RewriteIncoming, RewriteOutgoing)
             -> m ()
setRewriteFn :: Plugins theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing) -> m ()
setRewriteFn (Plugins TVar (PluginsState theme n hook config st)
tps) Maybe (RewriteIncoming, RewriteOutgoing)
f =
  IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar (PluginsState theme n hook config st)
tps ((PluginsState theme n hook config st
  -> PluginsState theme n hook config st)
 -> STM ())
-> (PluginsState theme n hook config st
    -> PluginsState theme n hook config st)
-> STM ()
forall a b. (a -> b) -> a -> b
$ \ps :: PluginsState theme n hook config st
ps@PluginsState{config
st
[hook]
[Cleanup]
Maybe theme
Maybe (RewriteIncoming, RewriteOutgoing)
Map PluginName (PluginName, Dynamic)
Map PluginName (TVar Dynamic)
Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: st
pluginsConfig :: config
pluginsPostHooks :: [hook]
pluginsTheme :: Maybe theme
pluginsPluginState :: Map PluginName (TVar Dynamic)
pluginsRouteFn :: Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: [Cleanup]
pluginsHandler :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsRewrite :: forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsState :: forall theme n hook config st.
PluginsState theme n hook config st -> st
pluginsConfig :: forall theme n hook config st.
PluginsState theme n hook config st -> config
pluginsPostHooks :: forall theme n hook config st.
PluginsState theme n hook config st -> [hook]
pluginsTheme :: forall theme n hook config st.
PluginsState theme n hook config st -> Maybe theme
pluginsPluginState :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (TVar Dynamic)
pluginsRouteFn :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map PluginName (PluginName, Dynamic)
pluginsOnShutdown :: forall theme n hook config st.
PluginsState theme n hook config st -> [Cleanup]
pluginsHandler :: forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
..} ->
    PluginsState theme n hook config st
ps { pluginsRewrite :: Maybe (RewriteIncoming, RewriteOutgoing)
pluginsRewrite = Maybe (RewriteIncoming, RewriteOutgoing)
f }

getRewriteFn :: (MonadIO m) =>
                Plugins theme n hook config st
             -> m (Maybe (RewriteIncoming, RewriteOutgoing))
getRewriteFn :: Plugins theme n hook config st
-> m (Maybe (RewriteIncoming, RewriteOutgoing))
getRewriteFn (Plugins TVar (PluginsState theme n hook config st)
tps) =
  IO (Maybe (RewriteIncoming, RewriteOutgoing))
-> m (Maybe (RewriteIncoming, RewriteOutgoing))
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Maybe (RewriteIncoming, RewriteOutgoing))
 -> m (Maybe (RewriteIncoming, RewriteOutgoing)))
-> IO (Maybe (RewriteIncoming, RewriteOutgoing))
-> m (Maybe (RewriteIncoming, RewriteOutgoing))
forall a b. (a -> b) -> a -> b
$ STM (Maybe (RewriteIncoming, RewriteOutgoing))
-> IO (Maybe (RewriteIncoming, RewriteOutgoing))
forall a. STM a -> IO a
atomically (STM (Maybe (RewriteIncoming, RewriteOutgoing))
 -> IO (Maybe (RewriteIncoming, RewriteOutgoing)))
-> STM (Maybe (RewriteIncoming, RewriteOutgoing))
-> IO (Maybe (RewriteIncoming, RewriteOutgoing))
forall a b. (a -> b) -> a -> b
$ (PluginsState theme n hook config st
 -> Maybe (RewriteIncoming, RewriteOutgoing))
-> STM (PluginsState theme n hook config st)
-> STM (Maybe (RewriteIncoming, RewriteOutgoing))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
forall theme n hook config st.
PluginsState theme n hook config st
-> Maybe (RewriteIncoming, RewriteOutgoing)
pluginsRewrite (STM (PluginsState theme n hook config st)
 -> STM (Maybe (RewriteIncoming, RewriteOutgoing)))
-> STM (PluginsState theme n hook config st)
-> STM (Maybe (RewriteIncoming, RewriteOutgoing))
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
tps

-- | NOTE: it is possible to set the URL type incorrectly here and not get a type error. How can we fix that ?
data Plugin url theme n hook config st = Plugin
    { Plugin url theme n hook config st -> PluginName
pluginName           :: PluginName
    , Plugin url theme n hook config st
-> Plugins theme n hook config st -> IO (Maybe PluginName)
pluginInit           :: Plugins theme n hook config st -> IO (Maybe Text)
    , Plugin url theme n hook config st -> [PluginName]
pluginDepends        :: [PluginName]  -- ^ plugins which much be initialized before this one can be
    , Plugin url theme n hook config st -> url -> [PluginName]
pluginToPathSegments :: url -> [Text] -- ^ convert url to URL path segments
    , Plugin url theme n hook config st -> hook
pluginPostHook       :: hook
    }

-- | initialize a plugin
initPlugin :: (Typeable url) =>
              Plugins theme n hook config st    -- ^ 'Plugins' handle
           -> Text                              -- ^ base URI to prepend to generated URLs
           -> Plugin url theme n hook config st -- ^ 'Plugin' to initialize
           -> IO (Maybe Text)                   -- ^ possible error message
initPlugin :: Plugins theme n hook config st
-> PluginName
-> Plugin url theme n hook config st
-> IO (Maybe PluginName)
initPlugin Plugins theme n hook config st
plugins PluginName
baseURI (Plugin{hook
[PluginName]
PluginName
url -> [PluginName]
Plugins theme n hook config st -> IO (Maybe PluginName)
pluginPostHook :: hook
pluginToPathSegments :: url -> [PluginName]
pluginDepends :: [PluginName]
pluginInit :: Plugins theme n hook config st -> IO (Maybe PluginName)
pluginName :: PluginName
pluginPostHook :: forall url theme n hook config st.
Plugin url theme n hook config st -> hook
pluginToPathSegments :: forall url theme n hook config st.
Plugin url theme n hook config st -> url -> [PluginName]
pluginDepends :: forall url theme n hook config st.
Plugin url theme n hook config st -> [PluginName]
pluginInit :: forall url theme n hook config st.
Plugin url theme n hook config st
-> Plugins theme n hook config st -> IO (Maybe PluginName)
pluginName :: forall url theme n hook config st.
Plugin url theme n hook config st -> PluginName
..}) =
    do -- putStrLn $ "initializing " ++ (Text.unpack pluginName)
       Plugins theme n hook config st
-> PluginName -> PluginName -> (url -> [PluginName]) -> IO ()
forall (m :: * -> *) url theme n hook config st.
(MonadIO m, Typeable url) =>
Plugins theme n hook config st
-> PluginName -> PluginName -> (url -> [PluginName]) -> m ()
addPluginRouteFn Plugins theme n hook config st
plugins PluginName
pluginName PluginName
baseURI url -> [PluginName]
pluginToPathSegments
       Plugins theme n hook config st -> hook -> IO ()
forall (m :: * -> *) theme n hook config st.
MonadIO m =>
Plugins theme n hook config st -> hook -> m ()
addPostHook Plugins theme n hook config st
plugins hook
pluginPostHook
       Plugins theme n hook config st -> IO (Maybe PluginName)
pluginInit Plugins theme n hook config st
plugins

paramsToQueryString :: [(Text, Text)] -> Text
paramsToQueryString :: [(PluginName, PluginName)] -> PluginName
paramsToQueryString [] = PluginName
forall a. Monoid a => a
mempty
paramsToQueryString [(PluginName, PluginName)]
ps = Builder -> PluginName
toStrictText (Builder -> PluginName) -> Builder -> PluginName
forall a b. (a -> b) -> a -> b
$ Builder
"?" Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> [Builder] -> Builder
forall a. Monoid a => [a] -> a
mconcat (Builder -> [Builder] -> [Builder]
forall a. a -> [a] -> [a]
intersperse Builder
"&" (((PluginName, PluginName) -> Builder)
-> [(PluginName, PluginName)] -> [Builder]
forall a b. (a -> b) -> [a] -> [b]
map (PluginName, PluginName) -> Builder
paramToQueryString [(PluginName, PluginName)]
ps) )
    where
      toStrictText :: Builder -> PluginName
toStrictText = Text -> PluginName
toStrict (Text -> PluginName) -> (Builder -> Text) -> Builder -> PluginName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
toLazyText

      isAlphaChar :: Char -> Bool
      isAlphaChar :: Char -> Bool
isAlphaChar Char
c    = (Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= Char
'A' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'Z') Bool -> Bool -> Bool
|| (Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= Char
'a' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'z')

      isDigitChar :: Char -> Bool
      isDigitChar :: Char -> Bool
isDigitChar Char
c    = (Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= Char
'0' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'9')

      isOk :: Char -> Bool
      isOk :: Char -> Bool
isOk Char
c = Char -> Bool
isAlphaChar Char
c Bool -> Bool -> Bool
|| Char -> Bool
isDigitChar Char
c Bool -> Bool -> Bool
|| Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Char
c (String
":@$-_.~" :: String)

      escapeChar :: Char -> Builder
escapeChar Char
c
          | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' '  = Char -> Builder
singleton Char
'+'
          | Char -> Bool
isOk Char
c    = Char -> Builder
singleton Char
c
          | Bool
otherwise = Builder
"%" Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<>
                        let hexDigit :: a -> Char
hexDigit a
n
                                | a
n a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= a
9 = String -> Char
forall a. [a] -> a
head (a -> String
forall a. Show a => a -> String
show a
n)
                                | a
n a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
10 = Char
'A'
                                | a
n a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
11 = Char
'B'
                                | a
n a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
12 = Char
'C'
                                | a
n a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
13 = Char
'D'
                                | a
n a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
14 = Char
'E'
                                | a
n a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
15 = Char
'F'
                        in case Int -> (Int -> Char) -> Int -> ShowS
forall a. (Integral a, Show a) => a -> (Int -> Char) -> a -> ShowS
showIntAtBase Int
16 Int -> Char
forall a. (Ord a, Num a, Show a) => a -> Char
hexDigit (Char -> Int
ord Char
c) String
"" of
                             []  -> Builder
"00"
                             [Char
x] -> String -> Builder
forall a. IsString a => String -> a
fromString [Char
'0',Char
x]
                             String
cs  -> String -> Builder
forall a. IsString a => String -> a
fromString String
cs

      escapeParam :: Text -> Builder
      escapeParam :: PluginName -> Builder
escapeParam PluginName
p = (Char -> Builder -> Builder) -> Builder -> PluginName -> Builder
forall a. (Char -> a -> a) -> a -> PluginName -> a
Text.foldr (\Char
c Builder
cs -> Char -> Builder
escapeChar Char
c Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
cs) Builder
forall a. Monoid a => a
mempty PluginName
p

      paramToQueryString :: (Text, Text) -> Builder
      paramToQueryString :: (PluginName, PluginName) -> Builder
paramToQueryString (PluginName
k,PluginName
v) = (PluginName -> Builder
escapeParam PluginName
k) Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
"=" Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (PluginName -> Builder
escapeParam PluginName
v)

------------------------------------------------------------------------------
-- serve
------------------------------------------------------------------------------

-- | serve requests using the 'Plugins' handle
--
-- NOTE: 
serve :: Plugins theme n hook config st -- ^ 'Plugins' handle
      -> PluginName -- ^ name of the plugin to handle this request
      -> [Text]     -- ^ unconsume path segments to pass to handler
      -> IO (Either String n)
serve :: Plugins theme n hook config st
-> PluginName -> [PluginName] -> IO (Either String n)
serve plugins :: Plugins theme n hook config st
plugins@(Plugins TVar (PluginsState theme n hook config st)
tvp) PluginName
prefix [PluginName]
path =
    do PluginsState theme n hook config st
ps <- STM (PluginsState theme n hook config st)
-> IO (PluginsState theme n hook config st)
forall a. STM a -> IO a
atomically (STM (PluginsState theme n hook config st)
 -> IO (PluginsState theme n hook config st))
-> STM (PluginsState theme n hook config st)
-> IO (PluginsState theme n hook config st)
forall a b. (a -> b) -> a -> b
$ TVar (PluginsState theme n hook config st)
-> STM (PluginsState theme n hook config st)
forall a. TVar a -> STM a
readTVar TVar (PluginsState theme n hook config st)
tvp
       let phs :: Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
phs = PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
forall theme n hook config st.
PluginsState theme n hook config st
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
pluginsHandler PluginsState theme n hook config st
ps
       case PluginName
-> Map
     PluginName (Plugins theme n hook config st -> [PluginName] -> n)
-> Maybe (Plugins theme n hook config st -> [PluginName] -> n)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup PluginName
prefix Map
  PluginName (Plugins theme n hook config st -> [PluginName] -> n)
phs of
         Maybe (Plugins theme n hook config st -> [PluginName] -> n)
Nothing  -> Either String n -> IO (Either String n)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String n -> IO (Either String n))
-> Either String n -> IO (Either String n)
forall a b. (a -> b) -> a -> b
$ String -> Either String n
forall a b. a -> Either a b
Left  (String -> Either String n) -> String -> Either String n
forall a b. (a -> b) -> a -> b
$ String
"Invalid plugin prefix: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ PluginName -> String
Text.unpack PluginName
prefix
         (Just Plugins theme n hook config st -> [PluginName] -> n
h) -> Either String n -> IO (Either String n)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String n -> IO (Either String n))
-> Either String n -> IO (Either String n)
forall a b. (a -> b) -> a -> b
$ n -> Either String n
forall a b. b -> Either a b
Right (n -> Either String n) -> n -> Either String n
forall a b. (a -> b) -> a -> b
$ (Plugins theme n hook config st -> [PluginName] -> n
h Plugins theme n hook config st
plugins [PluginName]
path)