{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} -- | Parsing Packets. module Text.Damn.Packet.Parser ( Packet(..), parse, parse', render, pktSubpacket, pktSubpacket', pktSubpacketL ) where import Prelude hiding (null) import Control.Applicative ((<$>), (<*>), (*>), many) import Control.Arrow (second) import Data.Attoparsec.Text hiding (parse) import Data.Char import Data.Map (fromList, toList) import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Text.Damn.Packet.Internal -- | A lens on 'pktSubpacket''. pktSubpacketL :: Functor f => (Maybe Packet -> f (Maybe Packet)) -> Packet -> f Packet pktSubpacketL afb s = setter s <$> afb (getter s) where getter p = pktSubpacket' p setter pkt m = pkt { pktBody = render <$> m } -- | Due to the way dAmn packets are designed, it's not possible to -- unambiguously determine whether a packet has a subpacket or just a body. -- Thus you will need to request a subpacket yourself. pktSubpacket :: Packet -> Either String Packet pktSubpacket Packet { pktBody = b } = case b of Nothing -> Left "Parent packet has no body!" Just pk -> parse pk {-# INLINE pktSubpacket #-} -- | Use when you don't care about the reason for parse failure. pktSubpacket' :: Packet -> Maybe Packet pktSubpacket' p = case pktSubpacket p of Left _ -> Nothing Right pk -> Just pk {-# INLINE pktSubpacket' #-} -- | 'render' converts a packet back into the dAmn text format. -- This is used by 'pktSubpacketL' to fulfill the lens laws, but you might -- find it useful if you want to write packets to dAmn. render :: Packet -> Text render (Packet cmd prm args b) = cmd <> maybe "" (" " <>) prm <> T.concat (map (\(k,v) -> "\n" <> k <> "=" <> v) (toList args)) <> maybe "" ("\n\n" <>) b -- | Parse some text, providing a packet or the reason for parse failure. parse :: Text -> Either String Packet parse str = if T.null body then packet else addBody packet where adjustedStr = accountForLoginSpace str (header, body) = second (T.drop 2) $ T.breakOn "\n\n" adjustedStr packet = parseOnly headP header addBody (Right s) = Right $ s { pktBody = Just body } addBody (Left l) = Left l -- | Parse some text, discarding any failure message. parse' :: Text -> Maybe Packet parse' s = case parse s of Right pk -> Just pk Left _ -> Nothing {-# INLINE parse' #-} headP :: Parser Packet headP = do cmd <- takeWhile1 (not . isSpace) prm <- option Nothing paramP args <- fromList <$> argsP return $ Packet cmd prm args Nothing where paramP = fmap Just (char ' ' *> takeWhile1 (not . isSpace)) "parameter" argsP = many ((,) <$> (char '\n' >> takeTill (=='=')) <*> (char '=' >> takeTill (=='\n'))) "arguments" accountForLoginSpace :: Text -> Text accountForLoginSpace s = if "login " `T.isPrefixOf` s then T.replace "\n\n" "\n" s else s