process-iterio-0.0.0: Data/IterIO/Process.hs
{-# LANGUAGE Safe #-}
{-# LANGUAGE OverloadedStrings #-}
-- | A library for enumerating OS process input and output with IterIO's stream transformers.
module Data.IterIO.Process
( enumProcess
, cmd
) where
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy as L
import System.Process hiding (readProcess)
import System.IO(hClose, hFlush, Handle)
import System.Exit(ExitCode(ExitFailure))
import Control.Monad.IO.Class(liftIO, MonadIO)
import Data.IterIO
import Control.Exception(ErrorCall(ErrorCall))
-- | Create an Onum that starts and executable at the given FilePath,
-- passing it args, and routing the process's stdout to the Onum's
-- output stream. If the executable fails, an exception is
-- thrown with the contents of stderr.
enumProcess :: FilePath -> [String] -> Onum ByteString IO a
enumProcess p args = inumBracket (mkProc p args) cleanup procInum_
-- | Create an Inum that starts and executable at the given FilePath,
-- and passing it args. The Inum's input stream is routed to stdin,
-- and stdout goes to the Inum's output stream. If the executable
-- fails, an exception is thrown with the contents of stderr.
cmd :: MonadIO m => FilePath -> [String] -> Inum ByteString ByteString m a
cmd p args = inumBracket (mkProc p args) cleanup procInum
-- Make the process object
mkProc :: MonadIO m => FilePath -> [String] -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
mkProc p args = liftIO $ do
createProcess (proc p args){
std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
}
-- Cleanup routine for process inums
cleanup :: MonadIO m => (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> m ()
cleanup (Just i, Just o, Just e, _) = liftIO $ do
hClose i
hClose o
hClose e
cleanup _ = error "the impossible happened"
-- Process Inum for an existing process
procInum :: (MonadIO m) => (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> Inum ByteString ByteString m a
procInum (Just i, Just o, Just e, pid) = mkInumM loop
where
loop = do
-- Grab any data we have for stidin
Chunk t eof <- chunkI
liftIO $ L.hPut i t
-- Close stdin if no more data. This will allow the process exit.
if eof
then liftIO $ hClose i
else liftIO $ hFlush i
done <- feedStdout i o e pid
if done
then return L.empty
else loop
procInum _ = error "unsupported configuration"
-- Process Onum for an existing process
procInum_ :: (MonadIO m) => (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> Onum ByteString m a
procInum_ (Just i, Just o, Just e, pid) = mkInumM loop
where
loop = do
done <- feedStdout i o e pid
if done
then return L.empty
else loop
procInum_ _ = error "unsupported configuration"
-- TODO: InumState is not exposed by the Inum module. Is it possible to add a type signature here?
--feedStdout :: (MonadIO m, ChunkData t) => Handle -> Handle -> Handle -> ProcessHandle -> Iter t (IterStateT (InumState t ByteString m a) m) Bool
feedStdout i o e pid = do
-- Close stdin if no more data. This will allow the process exit.
liftIO $ hClose i
-- After stdin exits, the exe will write to stdout and then close it.
maybeExitCode <- liftIO $ getProcessExitCode pid
case maybeExitCode of
Just (ExitFailure _) -> do
msg <- liftIO $ B.hGetContents e
throwI (ErrorCall (B8.unpack msg))
_ -> return ()
-- Grab any data we have on stdout
output <- liftIO $ L.hGetNonBlocking o 1024
-- Push proc's stdout to the inum's output stream
notListening <- ifeed output
-- Debate our future
return $ notListening || maybeExitCode /= Nothing