lambdabot-core-5.0.1: src/Lambdabot/Module.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Lambdabot.Module
( Module(..)
, newModule
, ModuleT(..)
, getRef
, getModuleName
, bindModule0
, bindModule1
, bindModule2
) where
import qualified Lambdabot.Command as Cmd
import Lambdabot.Config
import Lambdabot.Logging
import {-# SOURCE #-} Lambdabot.Monad
import Lambdabot.Util.Serial
import Control.Applicative
import Control.Concurrent (MVar)
import Control.Monad
import Control.Monad.Base
import Control.Monad.Reader (MonadReader(..), ReaderT(..))
import Control.Monad.Trans (MonadTrans(..), MonadIO(..))
import Control.Monad.Trans.Control
import System.Console.Haskeline.MonadException (MonadException)
------------------------------------------------------------------------
-- | The Module type class.
data Module st = Module {
-- | If the module wants its state to be saved, this function should
-- return a Serial.
--
-- The default implementation returns Nothing.
moduleSerialize :: !(Maybe (Serial st)),
-- | If the module maintains state, this method specifies the default state
-- (for example in case the state can't be read from a state).
--
-- The default implementation returns an error and assumes the state is
-- never accessed.
moduleDefState :: !(LB st),
-- | Is the module sticky? Sticky modules (as well as static ones) can't be
-- unloaded. By default, modules are not sticky.
moduleSticky :: !Bool,
-- | The commands the module listenes to.
moduleCmds :: !(ModuleT st LB [Cmd.Command (ModuleT st LB)]),
-- | Initialize the module. The default implementation does nothing.
moduleInit :: !(ModuleT st LB ()),
-- | Finalize the module. The default implementation does nothing.
moduleExit :: !(ModuleT st LB ()),
-- | Process contextual input. A plugin that implements 'contextual'
-- is able to respond to text not part of a normal command.
contextual
:: !(String -- the text
-> Cmd.Cmd (ModuleT st LB) ()) -- ^ the action
}
------------------------------------------------------------------------
newModule :: Module st
newModule = Module
{ contextual = \_ -> return ()
, moduleCmds = return []
, moduleExit = return ()
, moduleInit = return ()
, moduleSticky = False
, moduleSerialize = Nothing
, moduleDefState = return $ error "state not initialized"
}
--
-- | This transformer encodes the additional information a module might
-- need to access its name or its state.
--
newtype ModuleT st m a = ModuleT { runModuleT :: ReaderT (MVar st, String) m a }
deriving (Applicative, Functor, Monad, MonadTrans, MonadIO, MonadException, MonadConfig)
instance MonadLogging m => MonadLogging (ModuleT st m) where
getCurrentLogger = do
parent <- lift getCurrentLogger
self <- getModuleName
return (parent ++ ["Plugin", self])
logM a b c = lift (logM a b c)
instance MonadBase b m => MonadBase b (ModuleT st m) where
liftBase = lift . liftBase
instance MonadTransControl (ModuleT st) where
type StT (ModuleT st) a = a
liftWith f = do
r <- ModuleT ask
lift $ f $ \t -> runReaderT (runModuleT t) r
restoreT = lift
{-# INLINE liftWith #-}
{-# INLINE restoreT #-}
instance MonadBaseControl b m => MonadBaseControl b (ModuleT st m) where
type StM (ModuleT st m) a = ComposeSt (ModuleT st) m a
liftBaseWith = defaultLiftBaseWith
restoreM = defaultRestoreM
{-# INLINE liftBaseWith #-}
{-# INLINE restoreM #-}
getRef :: Monad m => ModuleT st m (MVar st)
getRef = ModuleT $ ask >>= return . fst
getModuleName :: Monad m => ModuleT mod m String
getModuleName = ModuleT $ ask >>= return . snd
-- | bind an action to the current module so it can be run from the plain
-- `LB' monad.
bindModule0 :: ModuleT mod LB a -> ModuleT mod LB (LB a)
bindModule0 act = bindModule1 (const act) >>= return . ($ ())
-- | variant of `bindModule0' for monad actions with one argument
bindModule1 :: (a -> ModuleT mod LB b) -> ModuleT mod LB (a -> LB b)
bindModule1 act = ModuleT $
ask >>= \st -> return (\val -> runReaderT (runModuleT $ act val) st)
-- | variant of `bindModule0' for monad actions with two arguments
bindModule2 :: (a -> b -> ModuleT mod LB c) -> ModuleT mod LB (a -> b -> LB c)
bindModule2 act = bindModule1 (uncurry act) >>= return . curry