kevin-0.5: Kevin/Damn/Packet.hs
module Kevin.Damn.Packet (
Packet(..),
command, parameter, args, body,
parsePacket,
parsePrivclasses,
subPacket,
fixLoginPacket,
okay,
readable,
null_,
notNull_
) where
import Control.Applicative (many, (<$>), (<$))
import Control.Exception (throw)
import Control.Lens
import Control.Monad (guard, liftM2)
import Data.Attoparsec.Text
import Data.Char
import qualified Data.Map as M
import Data.Maybe
import Data.Monoid
import qualified Data.Text as T
import Kevin.Base (KevinException(..), Privclass)
toMaybe :: (a -> Bool) -> a -> Maybe a
toMaybe f x = x <$ guard (f x)
notNull_ :: Prism' T.Text T.Text
notNull_ = prism' id $ toMaybe (not . T.null)
null_ :: Prism' T.Text T.Text
null_ = prism' id $ toMaybe T.null
data Packet = Packet { _command :: T.Text
, _parameter :: Maybe T.Text
, _args :: M.Map T.Text T.Text
, _body :: Maybe T.Text
} deriving Show
makeLenses ''Packet
parseCommand :: Parser T.Text
parseCommand = takeWhile1 (not . isSpace)
parseParam :: Parser (Maybe T.Text)
parseParam = do
char ' '
parm <- takeWhile1 (not . isSpace)
return $ Just parm
parseArgs :: Parser (M.Map T.Text T.Text)
parseArgs = (M.fromList <$>) . many $ do
char '\n'
c <- takeTill (=='=')
char '='
r <- takeTill (=='\n')
return (c,r)
parseHead :: Parser Packet
parseHead = do
c <- parseCommand
p <- option Nothing parseParam
a <- parseArgs
return $ Packet c p a Nothing
parsePacket :: T.Text -> Packet
parsePacket pack = case parseOnly parseHead top of
Left _ -> throw ParseFailure
Right res -> res & body .~ (T.drop 2 <$> toMaybe T.null b)
where
(top, b) = T.breakOn "\n\n" pack
getResult :: Either String a -> a
getResult x = let Right e = x in e
fixLoginPacket :: Packet -> Packet
fixLoginPacket pkt = if pkt^.command == "login"
then pkt & args %~ (<> getResult
(parseOnly parseArgs . T.cons '\n' . fromJust $ pkt^.body))
else pkt
subPacket :: Packet -> Maybe Packet
subPacket = (parsePacket <$>) . view body
okay :: Packet -> Bool
okay (Packet _ _ a _) = let e = a ^. at "e" in isNothing e || e == Just "ok"
parsePrivclasses :: T.Text -> [Privclass]
parsePrivclasses = map (liftM2 (,) (!! 1) (read . T.unpack . head) . T.splitOn ":")
. filter (not . T.null)
. T.splitOn "\n"
readable :: Packet -> T.Text
readable (Packet cmd param arg bod) = cmd
<> maybe "" (' ' `T.cons`) param
<> formattedArgs (M.toList arg)
<> maybe "" ("\n\n" `T.append`) bod
<> "\n\0"
where
formattedArgs [] = ""
formattedArgs q = T.append "\n" . T.intercalate "\n"
. map (uncurry (\x y -> x <> "=" <> y)) $ q