io-capture-0.1.0.0: src/System/IO/Capture.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
module System.IO.Capture
( capture
, captureStream
) where
import Control.Exception
import Control.Monad
import System.IO (hClose, hPutStr)
import System.Posix.IO
import System.Posix.Process
import System.Posix.Types (ProcessID)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Streaming as S
-- For haddocks
import Data.ByteString.Streaming (stdout)
-- | Capture an IO action's @stdout@, @stderr@, and any thrown exception. Waits
-- for the action to complete before returning.
--
-- @
-- > (out, _, _, _) <- 'capture' ('putStrLn' "foo")
-- > 'print' out
-- \"foo\"
--
-- > (_, err, _, _) <- 'capture' ('System.IO.hPutStrLn' 'System.IO.stderr' \"bar\")
-- > 'print' err
-- \"bar\"
--
-- > (_, _, exc, _) <- 'capture' 'undefined'
-- > 'print' exc
-- \"Prelude.undefined\"
-- @
capture :: IO a -> IO (LBS.ByteString, LBS.ByteString, LBS.ByteString, Maybe ProcessStatus)
capture act = do
(out, err, exc, pid) <- captureStream act
status <- getProcessStatus True True pid
(,,,)
<$> S.toLazy_ out
<*> S.toLazy_ err
<*> S.toLazy_ exc
<*> pure status
-- | Stream an IO action's @stdout@, @stderr@, and any thrown exception. Also
-- returns the spawned process's pid to @wait@ on (otherwise the process will
-- become a zombie after terminating), kill, etc.
--
-- @
-- import qualified Data.ByteString.Streaming as S
--
-- > let action = 'putStrLn' \"foo\" >> 'Control.Concurrent.threadDelay' 1000000
-- > (out, _, _, pid) <- 'captureStream' ('Control.Monad.replicateM_' 5 action)
-- > S.'stdout' out
-- foo
-- foo
-- foo
-- foo
-- foo
-- > 'getProcessStatus' True True pid
-- Just ('Exited' 'System.Exit.ExitSuccess')
-- @
captureStream
:: IO a
-> IO ( S.ByteString IO ()
, S.ByteString IO ()
, S.ByteString IO ()
, ProcessID
)
captureStream act = do
(out_r, out_w) <- createPipe
(err_r, err_w) <- createPipe
(exc_r, exc_w) <- createPipe
pid <- forkProcess $ do
closeFd out_r
closeFd err_r
closeFd exc_r
_ <- dupTo out_w stdOutput
_ <- dupTo err_w stdError
exc_wh <- fdToHandle exc_w
void act `catch` \(e :: SomeException) -> hPutStr exc_wh (show e)
closeFd out_w
closeFd err_w
hClose exc_wh
closeFd out_w
closeFd err_w
closeFd exc_w
out_rh <- fdToHandle out_r
err_rh <- fdToHandle err_r
exc_rh <- fdToHandle exc_r
pure ( S.hGetContents out_rh
, S.hGetContents err_rh
, S.hGetContents exc_rh
, pid
)