packages feed

kevin-0.4: Kevin/IRC/Packet.hs

module Kevin.IRC.Packet (
    Packet(..),
    parsePacket,
    readable
) where

import Prelude hiding (takeWhile)
import qualified Data.Text as T
import Data.Char
import Data.Attoparsec.Text
import Control.Applicative ((<|>), (<$>), (<*>), (*>), (<*))

data Packet = Packet { prefix :: Maybe T.Text
                     , command :: T.Text
                     , params :: [T.Text]
                     }
			| BadPacket deriving (Show)

badChars :: String
badChars = "\x20\x0\xd\xa"

spaces :: Parser T.Text
spaces = takeWhile1 isSpace

servername :: Parser T.Text
servername = takeWhile1 (inClass "a-z0-9.-")

username :: Parser T.Text
username = do
    n <- nick
    u <- option "" (T.cons <$> char '!' <*> user)
    h <- option "" (T.cons <$> char '@' <*> servername)
    return $ T.concat [n, u, h]

nick :: Parser T.Text
nick = T.cons <$> letter <*> takeWhile (inClass "a-zA-Z0-9[]\\`^{}-")

user :: Parser T.Text
user = takeWhile1 (notInClass badChars)

parsePrefix :: Parser T.Text
parsePrefix = username <|> servername

parseCommand :: Parser T.Text
parseCommand = takeWhile1 isAlpha <|>
               (do { a <- digit; b <- digit; c <- digit; return $ T.pack [a,b,c]})

parseParams :: Parser [T.Text]
parseParams = (colonParam <|> nonColonParam) `sepBy` spaces

colonParam :: Parser T.Text
colonParam = char ':' *> takeWhile (notInClass "\x0\xd\xa")

nonColonParam :: Parser T.Text
nonColonParam = takeWhile (notInClass badChars)

crlf :: Parser T.Text
crlf = string "\r\n"

messageBegin :: Parser (Maybe T.Text)
messageBegin = Just <$> (char ':' *> parsePrefix <* spaces)

packetParser :: Parser Packet
packetParser = do
    pre <- option Nothing messageBegin
    cmd <- T.map toUpper <$> parseCommand
    spaces
    par <- filter (not . T.null) <$> parseParams
    option "" crlf
    return $ Packet pre cmd par

parsePacket :: T.Text -> Packet
parsePacket str = case parseOnly packetParser str of
    Left _ -> BadPacket
    Right p -> p

showParams :: [T.Text] -> T.Text
showParams = T.unwords . map (\str -> if " " `T.isInfixOf` str then T.cons ':' str else str)

readable :: Packet -> T.Text
readable (Packet (Just str) cmd pms) = flip T.append "\r\n" $ T.unwords [T.cons ':' str, cmd, showParams pms]
readable (Packet Nothing c p) = flip T.append "\r\n" $ T.unwords [c, showParams p]
readable _ = ""