summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/Postdelay/Queue.hs108
-rw-r--r--lib/Postdelay/Scan.hs1
-rw-r--r--lib/Postdelay/TimeSpec.hs2
-rw-r--r--postdelay.cabal15
-rw-r--r--postdelay.nix21
-rw-r--r--src/Simple.hs77
6 files changed, 195 insertions, 29 deletions
diff --git a/lib/Postdelay/Queue.hs b/lib/Postdelay/Queue.hs
new file mode 100644
index 0000000..ac760ec
--- /dev/null
+++ b/lib/Postdelay/Queue.hs
@@ -0,0 +1,108 @@
1{-# LANGUAGE RankNTypes, RecordWildCards, ApplicativeDo, FlexibleContexts #-}
2
3module Postdelay.Queue
4 ( sendmail
5 , StoredMailF(..)
6 , StoredMail, StoredMail'
7 , sendStored, sendStored'
8 , StoreConfig, storeConfig
9 , pushStore, popStore
10 ) where
11
12import Postdelay.Types
13
14import Control.Monad.IO.Class (MonadIO(..))
15import Control.Monad.Catch (handle, MonadThrow(throwM))
16import Control.Monad.Trans.Maybe
17import Control.Monad.Reader
18import Control.Monad.Trans.Class
19import Control.Monad
20
21import System.IO (hClose, stderr)
22import System.IO.Error
23import GHC.IO.Exception
24import System.Process
25
26import Data.ByteString.Lazy as Lazy (ByteString)
27import qualified Data.ByteString.Lazy as Lazy.BS
28
29import Foreign.C.Error (Errno(..), ePIPE)
30
31import Data.Bool
32import Data.Conduit
33import Data.Time
34
35import qualified Options.Applicative as Opt (Parser)
36import Options.Applicative hiding (Parser)
37
38
39data StoredMailF a = StoredMail
40 { sendDelay :: Delay
41 , sender :: String
42 , recipients :: [String]
43 , message :: a
44 }
45
46type StoredMail = StoredMailF Lazy.ByteString
47type StoredMail' = StoredMailF (IO Lazy.ByteString)
48
49data StoreConfig = StoreConfig
50 { storeLocation :: FilePath
51 }
52
53
54storeConfig :: Opt.Parser StoreConfig
55storeConfig = do
56 storeLocation <- strOption $ mconcat
57 [ long "spool"
58 , short 's'
59 , metavar "PATH"
60 , help "Where to expect the mail spool"
61 ]
62 pure StoreConfig{..}
63
64
65sendmail' :: MonadIO m
66 => [String] -- ^ Arguments to sendmail
67 -> Lazy.ByteString -- ^ Message
68 -> m ()
69sendmail' args mail = liftIO . withCreateProcess cp $ \(Just inh) _ _ pHandle -> do
70 ignoreSigPipe $ do
71 Lazy.BS.hPutStr inh mail
72 hClose inh
73
74 ret <- waitForProcess pHandle
75 bool throwM (\_ -> return ()) (ret == ExitSuccess) $ ret
76 where
77 cp = (proc "sendmail" args)
78 { std_in = CreatePipe
79 }
80 ignoreSigPipe = handle $ \e -> case e of
81 IOError { ioe_type = ResourceVanished
82 , ioe_errno = Just ioe }
83 | Errno ioe == ePIPE -> return ()
84 _ -> throwM e
85
86sendmail :: MonadIO m
87 => String -- ^ Sender
88 -> [String] -- ^ Recipients
89 -> Lazy.ByteString -- ^ Message
90 -> m ()
91sendmail sender recipients = sendmail' $ ["-G", "-i", "-f", sender, "--"] ++ recipients
92
93sendStored :: MonadIO m => StoredMail -> m ()
94sendStored StoredMail{ sendDelay = Until{..}, ..} = void . runMaybeT $ do
95 now <- liftIO getCurrentTime
96 guard $ now >= releaseTime
97 lift $ sendmail sender recipients message
98
99sendStored' :: MonadIO m => StoredMail' -> m ()
100sendStored' StoredMail{..} = do
101 message' <- liftIO message
102 sendStored StoredMail{ message = message', .. }
103
104popStore :: (MonadIO m, MonadReader StoreConfig m) => Source m StoredMail'
105popStore = undefined
106
107pushStore :: (MonadIO m, MonadReader StoreConfig m) => Source m StoredMail -> m ()
108pushStore = undefined
diff --git a/lib/Postdelay/Scan.hs b/lib/Postdelay/Scan.hs
index 7f80818..6355928 100644
--- a/lib/Postdelay/Scan.hs
+++ b/lib/Postdelay/Scan.hs
@@ -3,7 +3,6 @@
3module Postdelay.Scan 3module Postdelay.Scan
4 ( scan 4 ( scan
5 , Delay(..) 5 , Delay(..)
6 , ParseError(..)
7 ) where 6 ) where
8 7
9import Postdelay.Types 8import Postdelay.Types
diff --git a/lib/Postdelay/TimeSpec.hs b/lib/Postdelay/TimeSpec.hs
index 951b1c0..f61d84e 100644
--- a/lib/Postdelay/TimeSpec.hs
+++ b/lib/Postdelay/TimeSpec.hs
@@ -124,7 +124,7 @@ dateSpec = label "Date" $ (>> scribe (prio' Default . time) midnight) <$> choice
124 scribe (prio' Assign . date.days) 1 124 scribe (prio' Assign . date.days) 1
125 ] 125 ]
126 , try $ do 126 , try $ do
127 let daySuffix = optional $ choice [ string' "st", string' "nd", string' "rd", string' "th" ] 127 let daySuffix = optional $ choice [ string ".", string' "st", string' "nd", string' "rd", string' "th" ]
128 (m, d) <- choice [ try $ (,) <$> monthName <* spaces <*> dayNumber False <* daySuffix 128 (m, d) <- choice [ try $ (,) <$> monthName <* spaces <*> dayNumber False <* daySuffix
129 , try $ flip (,) <$> dayNumber False <* daySuffix <* spaces <*> monthName 129 , try $ flip (,) <$> dayNumber False <* daySuffix <* spaces <*> monthName
130 ] 130 ]
diff --git a/postdelay.cabal b/postdelay.cabal
index ad8f59e..8be804b 100644
--- a/postdelay.cabal
+++ b/postdelay.cabal
@@ -19,9 +19,11 @@ cabal-version: >=1.10
19library 19library
20 exposed-modules: Postdelay.Scan 20 exposed-modules: Postdelay.Scan
21 , Postdelay.Types 21 , Postdelay.Types
22 , Postdelay.Queue
22 other-modules: Postdelay.TimeSpec 23 other-modules: Postdelay.TimeSpec
23 , Postdelay.TimeSpec.Units 24 , Postdelay.TimeSpec.Units
24 , Postdelay.TimeSpec.Utils 25 , Postdelay.TimeSpec.Utils
26 , Postdelay.PrioMap
25 build-depends: base >=4.9 && <5 27 build-depends: base >=4.9 && <5
26 , time >=1.6 && <2 28 , time >=1.6 && <2
27 , parsec >=3.1 && <4 29 , parsec >=3.1 && <4
@@ -40,6 +42,12 @@ library
40 , lens-datetime >=0.3 && <1 42 , lens-datetime >=0.3 && <1
41 , data-interval >=1.2 && <2 43 , data-interval >=1.2 && <2
42 , containers >=0.5.7 && <1 44 , containers >=0.5.7 && <1
45 , vector-space >=0.10 && <1
46 , process >=1.4 && <2
47 , conduit >=1.2 && <2
48 , transformers >=0.5 && <1
49 , bytestring >=0.10 && <1
50 , optparse-applicative >=0.13 && <1
43 hs-source-dirs: lib 51 hs-source-dirs: lib
44 default-language: Haskell2010 52 default-language: Haskell2010
45 53
@@ -49,6 +57,11 @@ executable postdelay-simple
49 -- other-extensions: 57 -- other-extensions:
50 build-depends: base >=4.9 && <5 58 build-depends: base >=4.9 && <5
51 , postdelay 59 , postdelay
52 , transformers >=0.5 && <1 60 , case-insensitive >=1.2 && <2
61 , exceptions >=0.8 && <1
62 , bytestring >=0.10 && <1
63 , conduit >=1.2 && <2
64 , optparse-applicative >=0.13 && <1
65 , mtl >=2.2 && <3
53 hs-source-dirs: src 66 hs-source-dirs: src
54 default-language: Haskell2010 67 default-language: Haskell2010
diff --git a/postdelay.nix b/postdelay.nix
index d6fce32..647b2ae 100644
--- a/postdelay.nix
+++ b/postdelay.nix
@@ -1,7 +1,8 @@
1{ mkDerivation, base, case-insensitive, containers, data-interval 1{ mkDerivation, base, bytestring, case-insensitive, conduit
2, exceptions, hsemail, lens, lens-datetime, list-t, megaparsec 2, containers, data-interval, exceptions, hsemail, lens
3, mime, mtl, old-time, parsec, stdenv, time, transformers, tz 3, lens-datetime, list-t, megaparsec, mime, mtl, old-time
4, units, units-defs 4, optparse-applicative, parsec, process, stdenv, time, transformers
5, tz, units, units-defs, vector-space
5}: 6}:
6mkDerivation { 7mkDerivation {
7 pname = "postdelay"; 8 pname = "postdelay";
@@ -10,11 +11,15 @@ mkDerivation {
10 isLibrary = true; 11 isLibrary = true;
11 isExecutable = true; 12 isExecutable = true;
12 libraryHaskellDepends = [ 13 libraryHaskellDepends = [
13 base case-insensitive containers data-interval exceptions hsemail 14 base bytestring case-insensitive conduit containers data-interval
14 lens lens-datetime list-t megaparsec mime mtl old-time parsec time 15 exceptions hsemail lens lens-datetime list-t megaparsec mime mtl
15 tz units units-defs 16 old-time optparse-applicative parsec process time transformers tz
17 units units-defs vector-space
18 ];
19 executableHaskellDepends = [
20 base bytestring case-insensitive conduit exceptions mtl
21 optparse-applicative
16 ]; 22 ];
17 executableHaskellDepends = [ base transformers ];
18 homepage = "https://git.yggdrasil.li/gkleen/pub/postdelay"; 23 homepage = "https://git.yggdrasil.li/gkleen/pub/postdelay";
19 description = "A postfix content filter for delaying delivery of mail"; 24 description = "A postfix content filter for delaying delivery of mail";
20 license = stdenv.lib.licenses.mit; 25 license = stdenv.lib.licenses.mit;
diff --git a/src/Simple.hs b/src/Simple.hs
index f461766..68524a8 100644
--- a/src/Simple.hs
+++ b/src/Simple.hs
@@ -1,24 +1,65 @@
1{-# LANGUAGE RecordWildCards, ApplicativeDo, FlexibleContexts #-}
2
1module Main (main) where 3module Main (main) where
2 4
3import Postdelay.Scan 5import Postdelay.Scan
6import Postdelay.Queue
4 7
5import Control.Monad.IO.Class 8import Data.Function (on)
6import Control.Monad.Trans.Except 9import Data.List
10import Data.CaseInsensitive (CI)
11import qualified Data.CaseInsensitive as CI
7 12
8 13import Control.Monad.IO.Class (MonadIO(..))
9main :: MonadIO m => m () 14import Control.Monad.Catch (MonadCatch)
15import Control.Monad.Reader
16
17import Data.ByteString.Lazy as Lazy (ByteString)
18import qualified Data.ByteString.Lazy as Lazy.BS
19
20import Data.ByteString.Lazy.Char8 as Lazy.Char8 (ByteString)
21import qualified Data.ByteString.Lazy.Char8 as Lazy.Char8.BS
22
23import Data.Conduit
24
25import System.Environment (getProgName)
26
27import qualified Options.Applicative as Opt (Parser)
28import Options.Applicative hiding (Parser)
29
30
31opts :: Opt.Parser (String, [String], StoreConfig)
32opts = do
33 sConfig <- storeConfig
34 sender <- strOption $ mconcat
35 [ long "sender"
36 , short 'f'
37 , metavar "SENDER"
38 ]
39 recipients <- some . strArgument $ mconcat
40 [ metavar "RECIPIENTS"
41 ]
42 pure (sender, recipients, sConfig)
43
44main :: (MonadIO m, MonadCatch m) => m ()
10main = do 45main = do
11 mailStr <- liftIO getContents 46 pName <- liftIO getProgName
12 delay <- runExceptT $ scan mailStr 47 (sender, recipients, sConfig) <- liftIO . execParser . info (helper <*> opts) $ mconcat
13 case delay of 48 [ fullDesc
14 Left err -> do 49 , progDesc "Queue an email for later delivery"
15 liftIO . putStrLn $ show err 50 , header pName
16 sendNow mailStr 51 ]
17 Right Nothing -> sendNow mailStr 52
18 Right (Just d) -> sendLater mailStr d 53 flip runReaderT sConfig $ do
19 54 mailStr <- liftIO getContents
20sendNow :: MonadIO m => String -> m () 55 delay <- scan mailStr
21sendNow = undefined 56 let
22 57 (headers, body) = break (== "") $ lines mailStr
23sendLater :: MonadIO m => String -> Delay -> m () 58 mailStr' = unlines $ filter (not . (isPrefixOf `on` CI.foldCase) "X-Delay:") headers ++ body
24sendLater = undefined 59
60 sendNow = sendmail sender recipients $ Lazy.Char8.BS.pack mailStr'
61 sendLater sendDelay = pushStore . yield $ StoredMail
62 { message = Lazy.Char8.BS.pack mailStr'
63 , ..
64 }
65 maybe sendNow sendLater delay