packages feed

darcs-monitor-0.3.7: EmailPatch.hs

{-  darcs-monitor - Darcs repository monitor
    Copyright © 2007 Antti-Juhani Kaijanaho
    Copyright © 2007 Benjamin Franksen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-}
module EmailPatch (emailPatch,defaultSendmail) where

import Control.Monad.Trans
import System.Directory
import System.Exit
import System.IO
import System.Process

import Config
import Darcs
import PatchData

import Paths_darcs_monitor

defaultSendmail :: String
defaultSendmail = "/usr/sbin/sendmail"

sendMail :: (MonadConfig m, MonadIO m) =>
            String -> String -> m Bool
sendMail recipient text = do
  sendmail <- asks confSendmailPath
  liftIO $ do (inh, outh, errh, ph) <-
                  runInteractiveProcess sendmail [recipient] Nothing Nothing
              hClose outh
              hPutStr inh text
              hClose inh
              err <- hGetContents errh
              hPutStr stderr err
              ec <- waitForProcess ph
              return (ec == ExitSuccess)

emailPatch :: (MonadConfig m, MonadIO m) =>
              String -> PatchData -> m Bool
emailPatch rec pd = do
  diff <- invokeDarcs ["diff"
                      ,"--match=hash " ++ patchHash pd
                      ,"--repo=" ++ patchRepo pd
                      ,"-u"]
  changes <- invokeDarcs ["changes"
                      ,"--match=hash " ++ patchHash pd
                      ,"--repo=" ++ patchRepo pd
                      ,"-s"]
  menc <- asks confCharset
  met <- asks confEmailTemplate
  maxDiff <- asks confMaxDiff
  et <- liftIO $ case met of
          Just s  -> return s
          Nothing -> do let s = patchRepoDir pd ++ "email-template"
                        ex <- doesFileExist s
                        if ex
                          then return s
                          else getDataFileName "default-template"
  tmpl <- liftIO $ readFile et
  let f hdr ('%':'%':r) = let r' = dropWhile (/= '\n') r
                              r'' = case r' of ('\n':r''') -> r'''
                                               _           -> r'
                          in f hdr r''
      f hdr ('%':r) = let (k,('%':r')) = span (/= '%') r
                          he s = if hdr
                                 then headerEncode menc s 
                                 else nhe s
                          nhe = showString
                          repl = case k of
                                   "RECIPIENT" -> nhe rec
                                   "DIFF"      -> he
                                                  $ case maxDiff of
                                                    Just md
                                                      | length diff > md
                                                        -> take md diff ++
                                                           "[...incomplete...]"
                                                    _ -> diff
                                   "CHANGES"   -> he changes
                                   "REPO"      -> he (patchRepo pd)
                                   "SHORTREPO" -> he (shortRepo pd)
                                   "AUTHOR"    -> he (patchAuthor pd ++ " ") .
                                                  showChar '<' .
                                                  nhe (patchAuthorEmail pd) .
                                                  showChar '>'
                                   "DATE"      -> nhe (patchDate pd)
                                   "HASH"      -> nhe (patchHash pd)
                                   "TITLE"     -> he (patchTitle pd)
                                   "COMMENT"   -> he (patchComment pd)
                                   _           -> nhe ('%' : k ++ "%")
                      in repl . f hdr r'
      f True ('\n':'\n':r) = showString "\n\n" . f False r
      f hdr (c:r) = showChar c . f hdr r
      f _ [] = id
      text' = f True tmpl
  let text = case menc of
               Just enc -> showString "MIME-Version: 1.0\n\
                                      \Content-Transfer-Encoding: 8bit\n\
                                      \Content-Type: text/plain; charset=" .
                           showString enc .
                           showChar '\n' .
                           text'
               Nothing -> text'
  liftIO $ putStrLn $ "Sending mail to " ++ rec ++ " about " ++ patchTitle pd
  sendMail rec (text "")


headerEncode :: Maybe String -> String -> ShowS
headerEncode menc str =
  case menc of
    Nothing  -> showString str
    Just enc -> let ws = words str
                    ws' = map (\s ->
                               if areSafe s
                               then s
                               else "=?" ++
                                    enc ++
                                    "?Q?" ++
                                    qEnc s "" ++
                                    "?="
                              ) ws
                in showString (unwords ws')
        where qEnc [] = id
              qEnc (' ':r) = showChar '_' . qEnc r
              qEnc ('_':r) = showString "=5F" . qEnc r
              qEnc (c:r)
                  | c `elem` safe = showChar c . qEnc r
                  | otherwise            = showChar '=' .
                                           showChar c1 .
                                           showChar c2 .
                                           qEnc r
                  where a = fromEnum c
                        a1 = a `div` 16
                        a2 = a `mod` 16
                        c1 = hds !! a1
                        c2 = hds !! a2
                        hds = "0123456789ABCDEF"

safe :: String
safe = "<>@-+.abcdefghijklmnopqrstuvwxyABCDEFGHIJKLMNOPQRSTUVWXY0123456789\
       \!\"#$%&\'()*,:;[]\\^_`{}|~/"

areSafe :: String -> Bool
areSafe = all (`elem` safe)

shortRepo :: PatchData -> String
shortRepo he 
    | last repo == '/' = reverse $ takeWhile (/= '/') $ tail $ reverse repo
    | '/' `elem` repo  = reverse $ takeWhile (/= '/') $ reverse repo
    | otherwise        = repo
      where repo = patchRepo he