|
Packit |
4b2029 |
{-# LANGUAGE DataKinds #-}
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
-- | The "System.Process.Typed" module from @typed-process@, but with
|
|
Packit |
4b2029 |
-- added conduit helpers.
|
|
Packit |
4b2029 |
module Data.Conduit.Process.Typed
|
|
Packit |
4b2029 |
( -- * Conduit specific stuff
|
|
Packit |
4b2029 |
createSink
|
|
Packit |
4b2029 |
, createSource
|
|
Packit |
4b2029 |
-- * Generalized functions
|
|
Packit |
4b2029 |
, withProcess
|
|
Packit |
4b2029 |
, withProcess_
|
|
Packit |
4b2029 |
, withLoggedProcess_
|
|
Packit |
4b2029 |
-- * Reexports
|
|
Packit |
4b2029 |
, module System.Process.Typed
|
|
Packit |
4b2029 |
) where
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
import System.Process.Typed hiding (withProcess, withProcess_)
|
|
Packit |
4b2029 |
import qualified System.Process.Typed as P
|
|
Packit |
4b2029 |
import Data.Conduit (ConduitM, (.|), runConduit)
|
|
Packit |
4b2029 |
import qualified Data.Conduit as C
|
|
Packit |
4b2029 |
import qualified Data.Conduit.Binary as CB
|
|
Packit |
4b2029 |
import Control.Monad.IO.Unlift
|
|
Packit |
4b2029 |
import qualified Data.ByteString as S
|
|
Packit |
4b2029 |
import System.IO (hClose)
|
|
Packit |
4b2029 |
import qualified Data.Conduit.List as CL
|
|
Packit |
4b2029 |
import qualified Data.ByteString.Lazy as BL
|
|
Packit |
4b2029 |
import Data.IORef (IORef, newIORef, readIORef, modifyIORef)
|
|
Packit |
4b2029 |
import Control.Exception (throwIO, catch)
|
|
Packit |
4b2029 |
import Control.Concurrent.Async (concurrently)
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
-- | Provide input to a process by writing to a conduit.
|
|
Packit |
4b2029 |
--
|
|
Packit |
4b2029 |
-- @since 1.2.1
|
|
Packit |
4b2029 |
createSink :: MonadIO m => StreamSpec 'STInput (ConduitM S.ByteString o m ())
|
|
Packit |
4b2029 |
createSink =
|
|
Packit |
4b2029 |
(\h -> C.addCleanup (\_ -> liftIO $ hClose h) (CB.sinkHandle h))
|
|
Packit |
4b2029 |
`fmap` createPipe
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
-- | Read output from a process by read from a conduit.
|
|
Packit |
4b2029 |
--
|
|
Packit |
4b2029 |
-- @since 1.2.1
|
|
Packit |
4b2029 |
createSource :: MonadIO m => StreamSpec 'STOutput (ConduitM i S.ByteString m ())
|
|
Packit |
4b2029 |
createSource =
|
|
Packit |
4b2029 |
(\h -> C.addCleanup (\_ -> liftIO $ hClose h) (CB.sourceHandle h))
|
|
Packit |
4b2029 |
`fmap` createPipe
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
-- | Internal function: like 'createSource', but stick all chunks into
|
|
Packit |
4b2029 |
-- the 'IORef'.
|
|
Packit |
4b2029 |
createSourceLogged
|
|
Packit |
4b2029 |
:: MonadIO m
|
|
Packit |
4b2029 |
=> IORef ([S.ByteString] -> [S.ByteString])
|
|
Packit |
4b2029 |
-> StreamSpec 'STOutput (ConduitM i S.ByteString m ())
|
|
Packit |
4b2029 |
createSourceLogged ref =
|
|
Packit |
4b2029 |
-- We do not add a cleanup action to close the handle, since in
|
|
Packit |
4b2029 |
-- withLoggedProcess_ we attempt to read from the handle twice
|
|
Packit |
4b2029 |
(\h ->
|
|
Packit |
4b2029 |
( CB.sourceHandle h
|
|
Packit |
4b2029 |
.| CL.iterM (\bs -> liftIO $ modifyIORef ref (. (bs:))))
|
|
Packit |
4b2029 |
)
|
|
Packit |
4b2029 |
`fmap` createPipe
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
-- | Same as 'P.withProcess', but generalized to 'MonadUnliftIO'.
|
|
Packit |
4b2029 |
--
|
|
Packit |
4b2029 |
-- @since 1.2.1
|
|
Packit |
4b2029 |
withProcess
|
|
Packit |
4b2029 |
:: MonadUnliftIO m
|
|
Packit |
4b2029 |
=> ProcessConfig stdin stdout stderr
|
|
Packit |
4b2029 |
-> (Process stdin stdout stderr -> m a)
|
|
Packit |
4b2029 |
-> m a
|
|
Packit |
4b2029 |
withProcess pc f = withRunInIO $ \run -> P.withProcess pc (run . f)
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
-- | Same as 'P.withProcess_', but generalized to 'MonadUnliftIO'.
|
|
Packit |
4b2029 |
--
|
|
Packit |
4b2029 |
-- @since 1.2.1
|
|
Packit |
4b2029 |
withProcess_
|
|
Packit |
4b2029 |
:: MonadUnliftIO m
|
|
Packit |
4b2029 |
=> ProcessConfig stdin stdout stderr
|
|
Packit |
4b2029 |
-> (Process stdin stdout stderr -> m a)
|
|
Packit |
4b2029 |
-> m a
|
|
Packit |
4b2029 |
withProcess_ pc f = withRunInIO $ \run -> P.withProcess_ pc (run . f)
|
|
Packit |
4b2029 |
|
|
Packit |
4b2029 |
-- | Run a process, throwing an exception on a failure exit code. This
|
|
Packit |
4b2029 |
-- will store all output from stdout and stderr in memory for better
|
|
Packit |
4b2029 |
-- error messages. Note that this will require unbounded memory usage,
|
|
Packit |
4b2029 |
-- so caveat emptor.
|
|
Packit |
4b2029 |
--
|
|
Packit |
4b2029 |
-- This will ignore any previous settings for the stdout and stderr
|
|
Packit |
4b2029 |
-- streams, and instead force them to use 'createSource'.
|
|
Packit |
4b2029 |
--
|
|
Packit |
4b2029 |
-- @since 1.2.3
|
|
Packit |
4b2029 |
withLoggedProcess_
|
|
Packit |
4b2029 |
:: MonadUnliftIO m
|
|
Packit |
4b2029 |
=> ProcessConfig stdin stdoutIgnored stderrIgnored
|
|
Packit |
4b2029 |
-> (Process stdin (ConduitM () S.ByteString m ()) (ConduitM () S.ByteString m ()) -> m a)
|
|
Packit |
4b2029 |
-> m a
|
|
Packit |
4b2029 |
withLoggedProcess_ pc inner = withUnliftIO $ \u -> do
|
|
Packit |
4b2029 |
stdoutBuffer <- newIORef id
|
|
Packit |
4b2029 |
stderrBuffer <- newIORef id
|
|
Packit |
4b2029 |
let pc' = setStdout (createSourceLogged stdoutBuffer)
|
|
Packit |
4b2029 |
$ setStderr (createSourceLogged stderrBuffer) pc
|
|
Packit |
4b2029 |
P.withProcess pc' $ \p -> do
|
|
Packit |
4b2029 |
a <- unliftIO u $ inner p
|
|
Packit |
4b2029 |
let drain src = unliftIO u (runConduit (src .| CL.sinkNull))
|
|
Packit |
4b2029 |
((), ()) <- drain (getStdout p) `concurrently`
|
|
Packit |
4b2029 |
drain (getStderr p)
|
|
Packit |
4b2029 |
checkExitCode p `catch` \ece -> do
|
|
Packit |
4b2029 |
stdout <- readIORef stdoutBuffer
|
|
Packit |
4b2029 |
stderr <- readIORef stderrBuffer
|
|
Packit |
4b2029 |
throwIO ece
|
|
Packit |
4b2029 |
{ eceStdout = BL.fromChunks $ stdout []
|
|
Packit |
4b2029 |
, eceStderr = BL.fromChunks $ stderr []
|
|
Packit |
4b2029 |
}
|
|
Packit |
4b2029 |
return a
|