{-# LANGUAGE OverloadedStrings #-}
module Foreign.Nix.Shellout.Helpers where
import Foreign.Nix.Shellout.Types
import qualified System.Process as P
import qualified Data.Text.IO as TIO
import qualified Data.Text as T
import qualified System.IO as SIO
import GHC.IO.Exception (IOErrorType(..), IOException(..), ExitCode)
import Foreign.C.Error (Errno(Errno), ePIPE)
import Data.Text (Text)
import Control.Error (ExceptT, withExceptT)
import Control.Concurrent (MVar, newEmptyMVar, forkIO, takeMVar, putMVar, killThread)
import Control.DeepSeq (rnf)
import Control.Exception (SomeException, throwIO, onException, try, mask, handle, evaluate)
import Control.Monad (unless)
import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as Text
readProcess :: ((Text, Text) -> ExitCode -> ExceptT e IO a)
-> Text
-> [Text]
-> NixAction e a
readProcess :: ((Text, Text) -> ExitCode -> ExceptT e IO a)
-> Text -> [Text] -> NixAction e a
readProcess (Text, Text) -> ExitCode -> ExceptT e IO a
with Text
exec [Text]
args = ExceptT (NixActionError e) IO a -> NixAction e a
forall e a. ExceptT (NixActionError e) IO a -> NixAction e a
NixAction (ExceptT (NixActionError e) IO a -> NixAction e a)
-> ExceptT (NixActionError e) IO a -> NixAction e a
forall a b. (a -> b) -> a -> b
$ do
(ExitCode
exc, Text
out, Text
err) <- IO (ExitCode, Text, Text)
-> ExceptT (NixActionError e) IO (ExitCode, Text, Text)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO
(IO (ExitCode, Text, Text)
-> ExceptT (NixActionError e) IO (ExitCode, Text, Text))
-> IO (ExitCode, Text, Text)
-> ExceptT (NixActionError e) IO (ExitCode, Text, Text)
forall a b. (a -> b) -> a -> b
$ CreateProcess -> TextEncoding -> Text -> IO (ExitCode, Text, Text)
readCreateProcessWithExitCodeAndEncoding
(FilePath -> [FilePath] -> CreateProcess
P.proc (Text -> FilePath
Text.unpack Text
exec) ((Text -> FilePath) -> [Text] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map Text -> FilePath
Text.unpack [Text]
args)) TextEncoding
SIO.utf8 Text
""
(e -> NixActionError e)
-> ExceptT e IO a -> ExceptT (NixActionError e) IO a
forall (m :: * -> *) e e' a.
Functor m =>
(e -> e') -> ExceptT e m a -> ExceptT e' m a
withExceptT
(\e
e -> NixActionError :: forall e. Text -> e -> NixActionError e
NixActionError
{ actionStderr :: Text
actionStderr = Text
err
, actionError :: e
actionError = e
e })
(ExceptT e IO a -> ExceptT (NixActionError e) IO a)
-> ExceptT e IO a -> ExceptT (NixActionError e) IO a
forall a b. (a -> b) -> a -> b
$ (Text, Text) -> ExitCode -> ExceptT e IO a
with (Text
out, Text
err) ExitCode
exc
readCreateProcessWithExitCodeAndEncoding
:: P.CreateProcess
-> SIO.TextEncoding
-> Text
-> IO (ExitCode, Text, Text)
readCreateProcessWithExitCodeAndEncoding :: CreateProcess -> TextEncoding -> Text -> IO (ExitCode, Text, Text)
readCreateProcessWithExitCodeAndEncoding CreateProcess
cp TextEncoding
encoding Text
input = do
let cp_opts :: CreateProcess
cp_opts = CreateProcess
cp
{ std_in :: StdStream
P.std_in = StdStream
P.CreatePipe
, std_out :: StdStream
P.std_out = StdStream
P.CreatePipe
, std_err :: StdStream
P.std_err = StdStream
P.CreatePipe }
CreateProcess
-> (Maybe Handle
-> Maybe Handle
-> Maybe Handle
-> ProcessHandle
-> IO (ExitCode, Text, Text))
-> IO (ExitCode, Text, Text)
forall a.
CreateProcess
-> (Maybe Handle
-> Maybe Handle -> Maybe Handle -> ProcessHandle -> IO a)
-> IO a
P.withCreateProcess CreateProcess
cp_opts ((Maybe Handle
-> Maybe Handle
-> Maybe Handle
-> ProcessHandle
-> IO (ExitCode, Text, Text))
-> IO (ExitCode, Text, Text))
-> (Maybe Handle
-> Maybe Handle
-> Maybe Handle
-> ProcessHandle
-> IO (ExitCode, Text, Text))
-> IO (ExitCode, Text, Text)
forall a b. (a -> b) -> a -> b
$
\(Just Handle
inh) (Just Handle
outh) (Just Handle
errh) ProcessHandle
ph -> do
Handle -> TextEncoding -> IO ()
SIO.hSetEncoding Handle
outh TextEncoding
encoding
Handle -> TextEncoding -> IO ()
SIO.hSetEncoding Handle
errh TextEncoding
encoding
Handle -> TextEncoding -> IO ()
SIO.hSetEncoding Handle
inh TextEncoding
encoding
Text
out <- Handle -> IO Text
TIO.hGetContents Handle
outh
Text
err <- Handle -> IO Text
TIO.hGetContents Handle
errh
IO () -> (IO () -> IO ()) -> IO ()
forall a. IO () -> (IO () -> IO a) -> IO a
withForkWait (() -> IO ()
forall a. a -> IO a
evaluate (() -> IO ()) -> () -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> ()
forall a. NFData a => a -> ()
rnf Text
out) ((IO () -> IO ()) -> IO ()) -> (IO () -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \IO ()
waitOut ->
IO () -> (IO () -> IO ()) -> IO ()
forall a. IO () -> (IO () -> IO a) -> IO a
withForkWait (() -> IO ()
forall a. a -> IO a
evaluate (() -> IO ()) -> () -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> ()
forall a. NFData a => a -> ()
rnf Text
err) ((IO () -> IO ()) -> IO ()) -> (IO () -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \IO ()
waitErr -> do
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Bool
T.null Text
input) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
IO () -> IO ()
ignoreSigPipe (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ Handle -> Text -> IO ()
TIO.hPutStr Handle
inh Text
input
IO () -> IO ()
ignoreSigPipe (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ Handle -> IO ()
SIO.hClose Handle
inh
IO ()
waitOut
IO ()
waitErr
Handle -> IO ()
SIO.hClose Handle
outh
Handle -> IO ()
SIO.hClose Handle
errh
ExitCode
ex <- ProcessHandle -> IO ExitCode
P.waitForProcess ProcessHandle
ph
(ExitCode, Text, Text) -> IO (ExitCode, Text, Text)
forall (m :: * -> *) a. Monad m => a -> m a
return (ExitCode
ex, Text
out, Text
err)
withForkWait :: IO () -> (IO () -> IO a) -> IO a
withForkWait :: IO () -> (IO () -> IO a) -> IO a
withForkWait IO ()
async IO () -> IO a
body = do
MVar (Either SomeException ())
waitVar <- IO (MVar (Either SomeException ()))
forall a. IO (MVar a)
newEmptyMVar :: IO (MVar (Either SomeException ()))
((forall a. IO a -> IO a) -> IO a) -> IO a
forall b. ((forall a. IO a -> IO a) -> IO b) -> IO b
mask (((forall a. IO a -> IO a) -> IO a) -> IO a)
-> ((forall a. IO a -> IO a) -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
restore -> do
ThreadId
tid <- IO () -> IO ThreadId
forkIO (IO () -> IO ThreadId) -> IO () -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ IO () -> IO (Either SomeException ())
forall e a. Exception e => IO a -> IO (Either e a)
try (IO () -> IO ()
forall a. IO a -> IO a
restore IO ()
async) IO (Either SomeException ())
-> (Either SomeException () -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= MVar (Either SomeException ()) -> Either SomeException () -> IO ()
forall a. MVar a -> a -> IO ()
putMVar MVar (Either SomeException ())
waitVar
let wait :: IO ()
wait = MVar (Either SomeException ()) -> IO (Either SomeException ())
forall a. MVar a -> IO a
takeMVar MVar (Either SomeException ())
waitVar IO (Either SomeException ())
-> (Either SomeException () -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (SomeException -> IO ())
-> (() -> IO ()) -> Either SomeException () -> IO ()
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either SomeException -> IO ()
forall e a. Exception e => e -> IO a
throwIO () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return
IO a -> IO a
forall a. IO a -> IO a
restore (IO () -> IO a
body IO ()
wait) IO a -> IO () -> IO a
forall a b. IO a -> IO b -> IO a
`onException` ThreadId -> IO ()
killThread ThreadId
tid
ignoreSigPipe :: IO () -> IO ()
ignoreSigPipe :: IO () -> IO ()
ignoreSigPipe = (IOException -> IO ()) -> IO () -> IO ()
forall e a. Exception e => (e -> IO a) -> IO a -> IO a
handle ((IOException -> IO ()) -> IO () -> IO ())
-> (IOException -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ \IOException
e -> case IOException
e of
IOError { ioe_type :: IOException -> IOErrorType
ioe_type = IOErrorType
ResourceVanished
, ioe_errno :: IOException -> Maybe CInt
ioe_errno = Just CInt
ioe }
| CInt -> Errno
Errno CInt
ioe Errno -> Errno -> Bool
forall a. Eq a => a -> a -> Bool
== Errno
ePIPE -> () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
IOException
_ -> IOException -> IO ()
forall e a. Exception e => e -> IO a
throwIO IOException
e