module Darcs.Email ( make_email, read_email ) where
import Data.Char ( ord, digitToInt, isHexDigit )
import Printer ( Doc, ($$), (<+>), (<>), text, empty, packedString, renderPS)
import FastPackedString ( PackedString, tailPS, nullPS, dropWhitePS,
indexPSW, packString, headPS, indexPS,
lengthPS, generatePS, dropPS, linesPS,
betweenLinesPS )
import System.IO.Unsafe ( unsafePerformIO )
import Foreign.Ptr ( Ptr, plusPtr )
import Foreign.Storable ( poke )
import Data.Word ( Word8 )
line_max :: Int
line_max = 75
qpencode :: PackedString -> PackedString
qpencode s = unsafePerformIO
-- Really only (3 + 2/75) * length or something in the worst case
$ generatePS (4 * lengthPS s) (\buf -> encode s line_max buf 0)
encode :: PackedString -> Int -> Ptr Word8 -> Int -> IO Int
encode ps _ _ bufi | nullPS ps = return bufi
encode ps n buf bufi = case indexPSW ps 0 of
c | c == newline ->
do poke (buf `plusPtr` bufi) newline
encode ps' line_max buf (bufi+1)
| n == 0 && lengthPS ps > 1 ->
do poke (buf `plusPtr` bufi) equals
poke (buf `plusPtr` (bufi+1)) newline
encode ps line_max buf (bufi + 2)
| (c == tab || c == space) ->
if nullPS ps' || indexPSW ps' 0 == newline
then do poke (buf `plusPtr` bufi) c
poke (buf `plusPtr` (bufi+1)) equals
poke (buf `plusPtr` (bufi+2)) newline
encode ps' line_max buf (bufi + 3)
else do poke (buf `plusPtr` bufi) c
encode ps' (n - 1) buf (bufi + 1)
| (c >= bang && c /= equals && c <= tilde) ->
do poke (buf `plusPtr` bufi) c
encode ps' (n - 1) buf (bufi + 1)
| n < 3 ->
encode ps 0 buf bufi
| otherwise ->
do let (x, y) = c `divMod` 16
h1 = intToUDigit x
h2 = intToUDigit y
poke (buf `plusPtr` bufi) equals
poke (buf `plusPtr` (bufi+1)) h1
poke (buf `plusPtr` (bufi+2)) h2
encode ps' (n - 3) buf (bufi + 3)
where ps' = tailPS ps
toWord8 :: Int -> Word8
toWord8 = fromIntegral
newline = toWord8 $ ord '\n'
tab = toWord8 $ ord '\t'
space = toWord8 $ ord ' '
bang = toWord8 $ ord '!'
tilde = toWord8 $ ord '~'
equals = toWord8 $ ord '='
intToUDigit i
| i >= 0 && i <= 9 = toWord8 (fromEnum '0') + i
| i >= 10 && i <= 15 = toWord8 (fromEnum 'A') + i - 10
| otherwise = error $ "intToUDigit: '"++show i++"'not a digit"
qpdecode :: PackedString -> PackedString
qpdecode s = unsafePerformIO
-- Add 1 as linesPS "\n" -> ["", ""] -> "\n\n"
$ generatePS (lengthPS s + 1) (\buf -> decode (linesPS s) buf 0)
decode :: [PackedString] -> Ptr Word8 -> Int -> IO Int
decode [] _ bufi = return bufi
decode (ps:pss) buf bufi
| nullPS (dropWhitePS ps)
= do poke (buf `plusPtr` bufi) newline
decode pss buf (bufi+1)
| is_equals && lengthPS ps >= 3 && isHexDigit c1 && isHexDigit c2
= do poke (buf `plusPtr` bufi)
(toWord8 $ digitToInt c1 * 16 + digitToInt c2)
decode (dropPS 3 ps:pss) buf (bufi+1)
| is_equals && nullPS (dropWhitePS (tailPS ps)) = decode pss buf bufi
| otherwise = do poke (buf `plusPtr` bufi) (indexPSW ps 0)
decode (tailPS ps:pss) buf (bufi+1)
where is_equals = headPS ps == '='
c1 = indexPS ps 1
c2 = indexPS ps 2
newline = toWord8 $ ord '\n'
toWord8 :: Int -> Word8
toWord8 = fromIntegral
make_email :: String -> (Maybe Doc) -> Doc -> (Maybe String) -> Doc
make_email repodir mcontents bundle mfilename =
text "DarcsURL:" <+> text repodir
$$ text "MIME-Version: 1.0"
$$ text "Content-Type: multipart/mixed; boundary=\"=_\""
$$ text ""
$$ text "--=_"
$$ (case mcontents of
Just contents ->
text "Content-Type: text/plain"
$$ text "Content-Transfer-Encoding: quoted-printable"
$$ text ""
$$ packedString (qpencode (renderPS contents))
$$ text ""
$$ text "--=_"
Nothing -> empty)
$$ text "Content-Type: text/x-darcs-patch" <>
(case mfilename of
Just filename -> text "; name=\"" <> text filename <> text "\""
Nothing -> empty)
$$ text "Content-Transfer-Encoding: quoted-printable"
$$ text "Content-Description: A darcs patch for your repository!"
$$ text ""
$$ packedString (qpencode (renderPS bundle))
$$ text "--=_--"
$$ text ""
$$ text "."
$$ text ""
$$ text ""
read_email :: PackedString -> PackedString
read_email s =
case betweenLinesPS
(packString "Content-Description: A darcs patch for your repository!")
(packString "--=_--") s of
Nothing -> s -- if it wasn't an email in the first place, just pass along.
Just s' -> qpdecode s'