module Bytor.Parse where
import Control.Applicative
import Control.Monad (void)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Attoparsec.ByteString (Parser)
import qualified Data.Attoparsec.ByteString as Atto
import Data.Sequence (Seq)
import Data.Time.Clock (UTCTime)
import Data.Word (Word8)
import System.Directory (listDirectory)
import Bytor.Types
pPhrase :: Parser ByteString
pPhrase = undefined
pMailbox :: Parser Mailbox
pMailbox = nameAddr <|> (Mailbox Nothing <$> pAddrSpec)
where nameAddr =
Mailbox <$> (Just <$> pPhrase)
<*> (Atto.word8 60 *> pAddrSpec <* Atto.word8 62)
pDotAtom :: Parser ByteString
pDotAtom = Atto.takeWhile (\ c -> atext c || c == 46)
pAddrSpec :: Parser Address
pAddrSpec = Address <$> pLocalPart <*> (Atto.word8 64 *> pDomain)
where pLocalPart = pDotAtom
pDomain = pDotAtom
atext :: Word8 -> Bool
atext c = c < 128 && c /= 40 && c /= 41
&& c /= 58 && c /= 59
&& c /= 60 && c /= 62
&& c /= 64 && c /= 91
&& c /= 92 && c /= 93
&& c /= 34
newtype MessageId = MessageId { _fromMessageId :: ByteString }
deriving (Eq, Show, Read)
getSubject :: RawEmail -> ByteString
getSubject RawEmail { reHeaders = hs } =
head [ subj | HeaderSubject subj <- hs ]
data RawEmail = RawEmail
{ reHeaders :: [Header]
, reBodies :: [ByteString]
} deriving (Eq, Show)
readBox :: FilePath -> IO [(FilePath, Either String RawEmail)]
readBox fp = do
paths <- listDirectory fp
let files = [ fp ++ "/" ++ p | p <- paths ]
raws <- sequence [ (,) f `fmap` B.readFile f | f <- files ]
return [ (f, Atto.parseOnly pEmail r) | (f, r) <- raws ]
pEmail :: Parser RawEmail
pEmail = do
headers <- Atto.manyTill pHeader pCrlf
body <- pBody
return (RawEmail headers [body])
pHeader :: Parser Header
pHeader = do
name <- Atto.takeWhile1 (\ c -> c /= 58 && not (isSpace c))
void $ Atto.word8 58
case name of
"OrigDate" -> HeaderOther "arglbargl" <$> foldingHeader
"Subject" -> HeaderSubject <$> foldingHeader
"Comments" -> HeaderComments <$> foldingHeader
"Keywords" -> HeaderKeywords <$> foldingHeader
_ -> HeaderOther name <$> foldingHeader
pDateTime :: Parser UTCTime
pDateTime = undefined
foldingHeader :: Parser ByteString
foldingHeader = do
skipSpace
line <- Atto.takeTill (== 10)
void $ Atto.word8 10
next <- Atto.peekWord8'
if isSpace next
then skipSpace *> (B.append line <$> foldingHeader)
else return line
skipSpace :: Parser ()
skipSpace = void $ Atto.takeWhile isSpace
isSpace :: (Num a, Eq a) => a -> Bool
isSpace c = c == 32 || c == 9
pCrlf :: Parser ()
pCrlf = void (Atto.word8 10)
pBody :: Parser ByteString
pBody = B.concat <$> Atto.many' (pText <* pCrlf)
pText :: Parser ByteString
pText = Atto.takeWhile (\ c -> c /= 10 && c /= 13)