module Network.Yogurt.Mud (
Mud, MudState, emptyMud,
RunMud, Output,
Hook,
Destination(..),
Pattern,
Var,
mkHook, mkPrioHook, setHook, rmHook, allHooks,
hPriority, hDestination, hPattern, hAction,
triggeredHook, matchedLine, before, group, after,
mkVar, setVar, readVar, modifyVar,
trigger, triggerJust, io,
liftIO, forkWithCallback
) where
import Prelude hiding (lookup)
import Data.IntMap (IntMap, empty, insert, delete, elems)
import Text.Regex.Posix ((=~))
import Network.Yogurt.Ansi
import Control.Monad.State
import Data.List (sort)
import Data.Function (on)
import Data.Ord (comparing)
import Data.Monoid (mconcat)
import Data.IORef
import Control.Concurrent (forkIO, ThreadId)
type Mud = StateT MudState IO
type RunMud = forall a. Mud a -> IO a
type Output = Destination -> String -> IO ()
data MudState = MudState
{ hooks :: IntMap Hook
, supply :: [Int]
, matchInfo :: Maybe MatchInfo
, mRunMud :: RunMud
, mOutput :: Output
}
emptyMud :: RunMud -> Output -> MudState
emptyMud = MudState empty [0..] Nothing
data Hook = Hook
{ hId :: Int
, hPriority :: Int
, hDestination :: Destination
, hPattern :: Pattern
, hAction :: Mud ()
}
instance Eq Hook where
(==) = (==) `on` hId
instance Ord Hook where
compare = flip $ mconcat [comparing hPriority, comparing hId]
instance Show Hook where
show (Hook hid prio dest pat _) = "Hook #" ++ show hid ++ " @" ++ show prio ++ " " ++ show dest ++ " [" ++ pat ++ "]"
data Destination
= Local
| Remote
deriving (Eq, Show, Read, Enum, Ord)
type Pattern = String
data MatchInfo = MatchInfo
{ mTriggeredHook :: Hook
, mMatchedLine :: String
, mBefore :: String
, mGroups :: [String]
, mAfter :: String
}
newtype Var a = Var (IORef a)
type Id = Int
mkId :: Mud Id
mkId = do
i <- gets (head . supply)
modify $ \s -> s { supply = tail (supply s) }
return i
updateHooks :: (IntMap Hook -> IntMap Hook) -> Mud ()
updateHooks f = modify $ \s -> s { hooks = f (hooks s) }
mkHook :: Destination -> Pattern -> Mud a -> Mud Hook
mkHook = mkPrioHook 0
mkPrioHook :: Int -> Destination -> Pattern -> Mud a -> Mud Hook
mkPrioHook prio dest pat act = do
hid <- mkId
let hook = Hook hid prio dest pat (act >> return ())
setHook hook
return hook
setHook :: Hook -> Mud ()
setHook hook = updateHooks $ insert (hId hook) hook
rmHook :: Hook -> Mud ()
rmHook = updateHooks . delete . hId
allHooks :: Mud [Hook]
allHooks = gets (sort . elems . hooks)
setMatchInfo :: Maybe MatchInfo -> Mud ()
setMatchInfo mi = modify $ \s -> s { matchInfo = mi }
getMatchInfo :: Mud MatchInfo
getMatchInfo = do
mi <- gets matchInfo
case mi of
Nothing -> fail "No match is available."
Just mi' -> return mi'
triggeredHook :: Mud Hook
triggeredHook = fmap mTriggeredHook getMatchInfo
matchedLine :: Mud String
matchedLine = fmap mMatchedLine getMatchInfo
before :: Mud String
before = fmap mBefore getMatchInfo
group :: Int -> Mud String
group n = fmap ((!! n) . mGroups) getMatchInfo
after :: Mud String
after = fmap mAfter getMatchInfo
mkVar :: a -> Mud (Var a)
mkVar = liftM Var . liftIO . newIORef
setVar :: Var a -> a -> Mud ()
setVar (Var var) = liftIO . writeIORef var
readVar :: Var a -> Mud a
readVar (Var var) = liftIO $ readIORef var
modifyVar :: Var a -> (a -> a) -> Mud ()
modifyVar (Var var) = liftIO . modifyIORef var
trigger :: Destination -> String -> Mud ()
trigger = triggerJust (const True)
triggerJust :: (Hook -> Bool) -> Destination -> String -> Mud ()
triggerJust test dest message = do
hs <- allHooks
case filter ok hs of
[] -> io dest message
(hook:_) -> fire message hook
where
ok hook = test hook && hDestination hook == dest && rmAnsi message =~ hPattern hook
fire :: String -> Hook -> Mud ()
fire message hook = do
oldMatchInfo <- gets matchInfo
setMatchInfo $ Just $ MatchInfo hook message before (match : groups) after
hAction hook
setMatchInfo oldMatchInfo
where
(before, match, after, groups) = rmAnsi message =~ hPattern hook :: (String, String, String, [String])
io :: Destination -> String -> Mud ()
io to msg = do
out <- gets mOutput
liftIO $ out to msg
forkWithCallback :: (RunMud -> IO ()) -> Mud ThreadId
forkWithCallback action = do
s <- get
liftIO . forkIO . action $ mRunMud s