ideas-1.0: src/Service/DomainReasoner.hs
{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances #-}
-----------------------------------------------------------------------------
-- Copyright 2011, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer : bastiaan.heeren@ou.nl
-- Stability : provisional
-- Portability : portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Service.DomainReasoner
( -- * Domain Reasoner data type
DomainReasoner, runDomainReasoner, runWithCurrent
, liftEither, MonadIO(..), catchError
-- * Update functions
, addExercises, addExercise, addExerciseService
, addServices, addService, addViews, addView
, addTestSuite
, addAliases, addScripts, setScriptDir
, setVersion, setFullVersion
-- * Accessor functions
, getExercises, getServices, getViews
, getVersion, getFullVersion, getTestSuite
, findExercise, findService
, readScript, defaultScript
) where
import Common.Library
import Common.Utils (Some(..))
import Common.Utils.TestSuite
import Control.Monad.Error
import Control.Monad.State
import Data.Maybe
import Service.FeedbackScript.Parser
import Service.Types
-----------------------------------------------------------------------
-- Domain Reasoner data type
newtype DomainReasoner a = DR { unDR :: ErrorT String (StateT Content IO) a }
data Content = Content
{ exercises :: [Some Exercise]
, services :: [Some Exercise] -> [Service]
, views :: [ViewPackage]
, aliases :: [(Id, Id)]
, scriptDir :: Maybe FilePath
, scripts :: [(Id, FilePath)]
, testSuite :: TestSuite
, version :: String
, fullVersion :: Maybe String
}
noContent :: Content
noContent = Content [] (const []) [] [] Nothing [] (return ()) [] Nothing
runDomainReasoner :: DomainReasoner a -> IO a
runDomainReasoner m = do
result <- evalStateT (runErrorT (unDR m)) noContent
case result of
Left msg -> fail msg
Right a -> return a
-- | Returns a run function, based on the current state, inside the monad
runWithCurrent :: DomainReasoner (DomainReasoner a -> IO a)
runWithCurrent =
get >>= \st -> return (runDomainReasoner . (put st >>))
liftEither :: Either String a -> DomainReasoner a
liftEither = either throwError return
-----------------------------------------------------------------------
-- Instance declarations
instance Monad DomainReasoner where
return a = DR (return a)
DR m >>= f = DR (m >>= unDR . f)
fail s = DR (throwError s)
instance MonadError String DomainReasoner where
throwError s = DR (throwError s)
catchError m f = DR (unDR m `catchError` (unDR . f))
instance MonadPlus DomainReasoner where
mzero = DR mzero
a `mplus` b = DR (unDR a `mplus` unDR b)
instance MonadState Content DomainReasoner where
get = DR get
put s = DR (put s)
instance MonadIO DomainReasoner where
liftIO m = DR (liftIO m)
-----------------------------------------------------------------------
-- Update functions
addExercises :: [Some Exercise] -> DomainReasoner ()
addExercises xs = modify $ \c -> c { exercises = xs ++ exercises c }
addExercise :: Some Exercise -> DomainReasoner ()
addExercise ex = addExercises [ex]
addExerciseService :: ([Some Exercise] -> Service) -> DomainReasoner ()
addExerciseService f = modify $ \c ->
c { services = \xs -> f xs : services c xs }
addServices :: [Service] -> DomainReasoner ()
addServices = mapM_ (addExerciseService . const)
addService :: Service -> DomainReasoner ()
addService s = addServices [s]
addViews :: [ViewPackage] -> DomainReasoner ()
addViews xs = modify $ \c -> c { views = xs ++ views c }
addView :: ViewPackage -> DomainReasoner ()
addView = addViews . return
addTestSuite :: TestSuite -> DomainReasoner ()
addTestSuite m = modify $ \c -> c { testSuite = testSuite c >> m }
addAliases :: [(Id, Id)] -> DomainReasoner ()
addAliases xs = modify $ \c -> c { aliases = xs ++ aliases c }
setScriptDir :: FilePath -> DomainReasoner ()
setScriptDir path = modify $ \c -> c { scriptDir = Just path }
addScripts :: [(Id, FilePath)] -> DomainReasoner ()
addScripts xs = modify $ \c -> c { scripts = xs ++ scripts c }
setVersion :: String -> DomainReasoner ()
setVersion s = modify $ \c -> c { version = s }
setFullVersion :: String -> DomainReasoner ()
setFullVersion s = modify $ \c -> c { fullVersion = Just s }
-----------------------------------------------------------------------
-- Accessor functions
getExercises :: DomainReasoner [Some Exercise]
getExercises = gets exercises
getServices :: DomainReasoner [Service]
getServices = gets (\c -> services c (exercises c))
getViews :: DomainReasoner [ViewPackage]
getViews = gets views
getVersion :: DomainReasoner String
getVersion = gets version
getFullVersion :: DomainReasoner String
getFullVersion = gets fullVersion >>= maybe getVersion return
getTestSuite :: DomainReasoner TestSuite
getTestSuite = gets testSuite
findExercise :: Id -> DomainReasoner (Some Exercise)
findExercise i = do
xs <- getExercises
table <- gets aliases
let res = fromMaybe i (lookup i table)
case [ a | a@(Some ex) <- xs, getId ex == res ] of
[this] -> return this
_ -> throwError $ "Exercise " ++ show i ++ " not found"
findService :: String -> DomainReasoner Service
findService txt = do
srvs <- getServices
case filter ((==txt) . showId) srvs of
[hd] -> return hd
[] -> throwError $ "No service " ++ txt
_ -> throwError $ "Ambiguous service " ++ txt
defaultScript :: Id -> DomainReasoner Script
defaultScript a = do
list <- gets scripts
maybe (return mempty) readScript (lookup a list)
-- | Returns an empty script if the file does not exist
readScript :: FilePath -> DomainReasoner Script
readScript file = do
path <- gets scriptDir
liftIO $ parseScript path file
`catchError`
\_ -> return mempty