packages feed

imap-0.2.0.3: src/Network/IMAP/Parsers/Utils.hs

module Network.IMAP.Parsers.Utils where

import Network.IMAP.Types

import Data.Attoparsec.ByteString
import qualified Data.Attoparsec.ByteString as AP
import Data.Word8
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString as BS
import Data.Either.Combinators (rightToMaybe)
import Control.Monad (liftM)

eatUntilClosingParen :: Parser BSC.ByteString
eatUntilClosingParen = scan 0 hadClosedAllParens <* word8 _parenright

hadClosedAllParens :: Int -> Word8 -> Maybe Int
hadClosedAllParens openingParenCount char
  | char == _parenright =
    if openingParenCount == 1
      then Nothing
      else Just $ openingParenCount - 1
  | char == _parenleft = Just $ openingParenCount + 1
  | otherwise =  Just openingParenCount


parseEmailList :: Parser [EmailAddress]
parseEmailList = string "(" *> parseEmail `sepBy` word8 _space <* string ")"

parseEmail :: Parser EmailAddress
parseEmail = do
  string "(\""
  label <- nilOrValue $ AP.takeWhile1 (/= _quotedbl)
  string "\" NIL \""

  emailUsername <- AP.takeWhile1 (/= _quotedbl)
  string "\" \""
  emailDomain <- AP.takeWhile1 (/= _quotedbl)
  string "\")"
  let fullAddr = decodeUtf8 $ BSC.concat [emailUsername, "@", emailDomain]

  return $ EmailAddress (liftM decodeUtf8 label) fullAddr

nilOrValue :: Parser a -> Parser (Maybe a)
nilOrValue parser = rightToMaybe <$> AP.eitherP (string "NIL") parser

parseQuotedText :: Parser T.Text
parseQuotedText = do
  word8 _quotedbl
  date <- AP.takeWhile1 (/= _quotedbl)
  word8 _quotedbl

  return . decodeUtf8 $ date

parseNameAttribute :: Parser NameAttribute
parseNameAttribute = do
  string "\\"
  name <- AP.takeWhile1 isAtomChar
  return $ case name of
          "Noinferiors" -> Noinferiors
          "Noselect" -> Noselect
          "Marked" -> Marked
          "Unmarked" -> Unmarked
          "HasNoChildren" -> HasNoChildren
          _ -> OtherNameAttr $ decodeUtf8 name

parseListLikeResp :: BSC.ByteString -> Parser UntaggedResult
parseListLikeResp prefix = do
  string prefix
  string " ("
  nameAttributes <- parseNameAttribute `sepBy` word8 _space

  string ") \""
  delimiter <- liftM (decodeUtf8 . BS.singleton) AP.anyWord8
  string "\" "
  name <- liftM decodeUtf8 $ AP.takeWhile1 (/= _cr)

  let actualName = T.dropAround (== '"') name
  return $ ListR nameAttributes delimiter actualName

isAtomChar :: Word8 -> Bool
isAtomChar c = isLetter c 
            || isNumber c 
            || c == _hyphen 
            || c == _quotedbl 
            || c == _period 
            || c == _plus 
            || c == _dollar
            || c == _ampersand
            || c == _quotesingle
            || c == _comma
            || c == _hyphen
            || c == _period
            || c == _slash
            || (c >= 0x3a && c <= 0x3c)
            || (c >= 0x3e && c <= 0x40)
            || c == _backslash
            || (c >= 0x5e && c <= 0x60)
            || c == _tilde

toInt :: BSC.ByteString -> Either ErrorMessage Int
toInt bs = if null parsed
    then Left errorMsg
    else Right . fst . head $ parsed
  where parsed = reads $ BSC.unpack bs
        errorMsg = T.concat ["Count not parse '", decodeUtf8 bs, "' as an integer"]

parseNumber :: (Int -> a) -> BSC.ByteString ->
  BSC.ByteString -> Parser (Either ErrorMessage a)
parseNumber constructor prefix postfix = do
  if not . BSC.null $ prefix
    then string prefix <* word8 _space
    else return BSC.empty
  number <- takeWhile1 isDigit
  if not . BSC.null $ postfix
    then word8 _space *> string postfix
    else return BSC.empty

  return $ liftM constructor (toInt number)