danibot-0.2.0.0: lib/Network/Danibot.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NumDecimals #-}
{-# LANGUAGE ViewPatterns #-}
module Network.Danibot (
Command
, Description
, dumbHandler
, lookupHandler
, isUpHandler
, helpHandler
, dispatch
) where
import Data.Monoid
import Data.Char
import Data.Map
import Data.List
import qualified Data.Map as Map
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Attoparsec.Text as Atto
import Text.Read
import Control.Applicative
import Control.Concurrent
import Control.Concurrent.Async
import Network
import System.IO
import Control.Exception
type Command = Text
type Description = Text
{-| Waits half a second, does nothing.
-}
dumbHandler :: (Text -> IO Text,Description)
dumbHandler =
let go _ = do
threadDelay 0.5e6
return "Nothing was done."
in (go,"")
{-| Performs lookups against an in-memory map.
-}
lookupHandler :: Map Text Text -> (Text -> IO Text,Description)
lookupHandler aMap =
let go key = pure (maybe "Entry not found." id (Map.lookup key aMap))
in (go,"key")
{-| Is the given port listening on the given host?
-}
isUpHandler :: (Text -> IO Text,Description)
isUpHandler =
let go (Text.break isSpace . Text.strip -> (Text.strip -> host,Text.strip -> port)) = do
case (extractAddress host, readMaybe (Text.unpack port)) of
(Right host', Just numericport) -> do
let attempt =
catchAny
(bracket (Network.connectTo (Text.unpack host') (PortNumber (fromInteger numericport)))
hClose
(\_ -> pure "The port is listening."))
(\e -> pure ("Exception when trying to connect: " <> Text.pack (show e)))
either id id <$> race attempt (threadDelay 3e6 *> pure "Timeout.")
_ -> pure "Expected arguments: host port"
in (go, "host port")
where
catchAny :: IO a -> (SomeException -> IO a) -> IO a
catchAny = catch
extractAddress :: Text -> Either String Text
extractAddress txt = Atto.parseOnly extractor txt
where
extractor =
(Atto.string "<" *> Atto.takeTill (\c -> c == '|')
*> Atto.string "|"
*> Atto.takeTill (\c -> c == '>')
<* Atto.string ">")
<|>
Atto.takeText
{-| Builds the help message.
-}
helpHandler :: [(Command,(Text -> IO Text,Description))] -> (Text -> IO Text, Description)
helpHandler handlers =
let helptext = mconcat (intersperse "\n" [k <> " " <> v | (k,(_,v)) <- handlers])
in (\_ -> pure helptext,"")
splitWord :: Text -> (Text,Text)
splitWord (Text.break isSpace . Text.strip -> (Text.strip -> word,Text.strip -> rest)) = (word,rest)
{-| Builds a handler from a list of command names and sub-handlers.
-}
dispatch :: [(Command,(Text -> IO Text,x))] -> Text -> IO Text
dispatch (Map.fromList -> handlers) (splitWord -> (key,request)) =
case Map.lookup key handlers of
Just (f,_) -> f request
Nothing -> pure "Unknown command."