Blame Data/Conduit/Process/Typed.hs

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