shell-conduit-0.0: src/Data/Conduit/Shell/Process.hs
{-# LANGUAGE FlexibleContexts #-}
-- | Reading from the process.
module Data.Conduit.Shell.Process
(-- * Running scripts
run
-- * Running processes
,Data.Conduit.Shell.Process.shell
,Data.Conduit.Shell.Process.proc
-- * I/O chunks
,withRights
,redirect
,quiet
,writeChunks
,discardChunks
-- * Low-level internals
,conduitProcess
)
where
import Data.Conduit.Shell.Types
import Control.Applicative
import qualified Control.Exception as E
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Loop
import Control.Monad.Trans.Resource
import Data.ByteString
import qualified Data.ByteString as S
import Data.Conduit
import Data.Conduit.List (sourceList)
import qualified Data.Conduit.List as CL
import Data.Conduit.Process
import Data.Either
import Data.Maybe
import System.Exit (ExitCode(..))
import System.IO
import qualified System.Process
-- | Run a shell command.
shell :: (MonadResource m) => String -> Conduit Chunk m Chunk
shell = conduitProcess . System.Process.shell
-- | Run a shell command.
proc :: (MonadResource m) => String -> [String] -> Conduit Chunk m Chunk
proc px args = conduitProcess (System.Process.proc px args)
-- | Size of buffer used to read from process.
bufSize :: Int
bufSize = 64 * 1024
-- | Do something with just the rights.
withRights :: (Monad m)
=> Conduit ByteString m ByteString -> Conduit Chunk m Chunk
withRights f =
getZipConduit
(ZipConduit f' *>
ZipConduit g')
where f' =
CL.mapMaybe (either (const Nothing) Just) =$=
f =$=
CL.map Right
g' = CL.filter isLeft
-- | Redirect the given chunk type to the other type.
redirect :: Monad m
=> ChunkType -> Conduit Chunk m Chunk
redirect ty =
CL.map (\c' ->
case c' of
Left x' ->
case ty of
Stderr -> Right x'
Stdout -> c'
Right x' ->
case ty of
Stderr -> c'
Stdout -> Left x')
-- | Discard any output from the command: make it quiet.
quiet :: (Monad m,MonadIO m) => Conduit Chunk m Chunk -> Conduit Chunk m Chunk
quiet m = m $= discardChunks
-- | Run a shell scripting conduit.
run :: (MonadIO m,MonadBaseControl IO m)
=> Conduit Chunk (ShellT m) Chunk -> m ()
run p =
runResourceT
(runShellT (sourceList [] $=
p $$
writeChunks))
-- | Write chunks to stdout and stderr.
writeChunks :: (MonadIO m)
=> Consumer Chunk m ()
writeChunks =
awaitForever
(\c ->
case c of
Left e -> liftIO (S.hPut stderr e)
Right o -> liftIO (S.hPut stdout o))
-- | Discard all chunks.
discardChunks :: (MonadIO m)
=> Consumer Chunk m ()
discardChunks = awaitForever (const (return ()))
-- | Conduit of process.
conduitProcess
:: (MonadResource m)
=> CreateProcess
-> Conduit Chunk m Chunk
conduitProcess cp = bracketP createp closep $ \(Just cin, Just cout, _, ph) -> do
end <- repeatLoopT $ do
-- if process's outputs are available, then yields them.
repeatLoopT $ do
b <- liftIO $ hReady' cout
when (not b) exit
out <- liftIO $ S.hGetSome cout bufSize
void $ lift . lift $ yield (Right out)
-- if process exited, then exit
end <- liftIO $ getProcessExitCode ph
when (isJust end) $ exitWith end
inp <- lift await
case inp of
-- if upper stream ended, then exit
Nothing -> exitWith Nothing
Just c ->
case c of
-- pass along errors to next process
Left{} -> lift (leftover c)
-- write stdin into this process
Right s ->
liftIO (do S.hPut cin s
hFlush cin)
-- uppstream or process is done.
-- process rest outputs.
liftIO $ hClose cin
repeatLoopT $ do
out <- liftIO $ S.hGetSome cout bufSize
when (S.null out) exit
lift $ yield (Right out)
ec <- liftIO $ maybe (waitForProcess' ph) return end
case ec of
ExitSuccess -> return ()
ExitFailure i -> lift (monadThrow (ShellExitFailure i))
where
createp = createProcess cp
{ std_in = CreatePipe
, std_out = CreatePipe
}
closep (Just cin, Just cout, _, ph) = do
hClose cin
hClose cout
_ <- waitForProcess' ph
return ()
closep _ = error "Data.Conduit.Process.closep: Unhandled case"
hReady' h =
hReady h `E.catch` \(E.SomeException _) -> return False
waitForProcess' ph =
waitForProcess ph `E.catch` \(E.SomeException _) -> return ExitSuccess