module Pipes.Shell
(
pipeCmdEnv, pipeCmd, pipeCmd'
, producerCmdEnv, producerCmd, producerCmd'
, consumerCmdEnv, consumerCmd
, Cmd, Cmd'
, cmdEnv, cmd, cmd'
, (>?>)
, markEnd
, ignoreErr, ignoreOut
, runShell
) where
import Control.Monad
import Pipes
import qualified Pipes.ByteString as PBS
import Pipes.Core
import qualified Pipes.Prelude as P
import Pipes.Safe hiding (handle)
import Control.Concurrent hiding (yield)
import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Concurrent.STM.TBMChan
import qualified System.IO as IO
import System.Process
import qualified Data.ByteString as BS
class Cmd cmd where
cmdEnv :: Maybe [(String,String)] -> String -> cmd
cmd :: Cmd cmd => String -> cmd
cmd = cmdEnv Nothing
instance Cmd cmd => Cmd (String -> cmd) where
cmdEnv env' binary arg = cmdEnv env' $ binary ++ " " ++ arg
instance MonadSafe m =>
Cmd (Pipe
(Maybe BS.ByteString)
(Either BS.ByteString BS.ByteString)
m ()) where
cmdEnv = pipeCmdEnv
instance MonadSafe m =>
Cmd (Producer
(Either BS.ByteString BS.ByteString)
m ()) where
cmdEnv = producerCmdEnv
instance MonadSafe m =>
Cmd (Consumer
(Maybe BS.ByteString)
m ()) where
cmdEnv = consumerCmdEnv
class Cmd' cmd where
cmd' :: String -> cmd
instance Cmd' cmd => Cmd' (String -> cmd) where
cmd' binary arg = cmd' $ binary ++ " " ++ arg
instance MonadSafe m =>
Cmd' (Pipe (Maybe BS.ByteString) BS.ByteString m ()) where
cmd' = pipeCmd'
instance MonadSafe m =>
Cmd' (Producer BS.ByteString m ()) where
cmd' = producerCmd'
instance MonadSafe m =>
Cmd' (Consumer (Maybe BS.ByteString) m ()) where
cmd' = consumerCmd
pipeCmdEnv :: MonadSafe m =>
Maybe [(String,String)] ->
String ->
Pipe (Maybe BS.ByteString) (Either BS.ByteString BS.ByteString) m ()
pipeCmdEnv env' cmdStr = bracket (aquirePipe env' cmdStr) releasePipe $
\(stdin, stdout, stderr) -> do
chan <- liftIO $ newTBMChanIO 4
_ <- liftIO . forkIO $
handlesToChan stdout stderr chan
body stdin chan
where
body stdin chan = do
got <- await
case got of
Nothing -> do
liftIO $ IO.hClose stdin
fromTBMChan chan
Just val -> do
liftIO $ BS.hPutStr stdin val
yieldOne chan
body stdin chan
yieldOne chan = do
mLine <- liftIO $ atomically $ tryReadTBMChan chan
whenJust (join mLine)
yield
handlesToChan stdout stderr chan = do
out <- async $ toTBMChan chan $ do
PBS.fromHandle stdout >-> P.map Right
liftIO $ IO.hClose stdout
err <- async $ toTBMChan chan $ do
PBS.fromHandle stderr >-> P.map Left
liftIO $ IO.hClose stderr
forM_ [out, err] wait
atomically $ closeTBMChan chan
pipeCmd :: MonadSafe m =>
String ->
Pipe (Maybe BS.ByteString) (Either BS.ByteString BS.ByteString) m ()
pipeCmd = pipeCmdEnv Nothing
pipeCmd' :: MonadSafe m =>
String ->
Pipe (Maybe BS.ByteString) BS.ByteString m ()
pipeCmd' cmdStr = pipeCmd cmdStr >-> ignoreErr
producerCmdEnv :: MonadSafe m =>
Maybe [(String, String)] ->
String ->
Producer (Either BS.ByteString BS.ByteString) m ()
producerCmdEnv env' cmdStr = yield Nothing >-> pipeCmdEnv env' cmdStr
producerCmd :: MonadSafe m =>
String ->
Producer (Either BS.ByteString BS.ByteString) m ()
producerCmd = producerCmdEnv Nothing
producerCmd' :: MonadSafe m =>
String ->
Producer BS.ByteString m ()
producerCmd' cmdStr = producerCmd cmdStr >-> ignoreErr
consumerCmdEnv :: MonadSafe m =>
Maybe [(String,String)] ->
String ->
Consumer (Maybe BS.ByteString) m ()
consumerCmdEnv env' cmdStr = pipeCmdEnv env' cmdStr >-> void await
consumerCmd :: MonadSafe m =>
String ->
Consumer (Maybe BS.ByteString) m ()
consumerCmd = consumerCmdEnv Nothing
(>?>) :: Monad m =>
Proxy a' a () b m r ->
Proxy () (Maybe b) c' c m r ->
Proxy a' a c' c m r
a >?> b = markEnd a >-> b
infixl 7 >?>
markEnd :: Monad m =>
Proxy a' a b' b m r ->
Proxy a' a b' (Maybe b) m r
markEnd pipe = do
result <- for pipe (respond . Just)
_ <- respond Nothing
return result
ignoreErr :: (Monad m) =>
Pipe (Either BS.ByteString BS.ByteString) BS.ByteString m ()
ignoreErr = forever $ do
val <- await
case val of
Left _ -> return ()
Right x -> yield x
ignoreOut :: (Monad m) => Pipe (Either BS.ByteString BS.ByteString) BS.ByteString m ()
ignoreOut = forever $ do
val <- await
case val of
Left x -> yield x
Right _ -> return ()
runShell :: Effect (SafeT IO) r -> IO r
runShell = runSafeT . runEffect
whenJust :: Monad m => Maybe a -> (a -> m ()) -> m ()
whenJust (Just x) action = action x
whenJust Nothing _ = return ()
fromTBMChan :: (MonadIO m) => TBMChan a -> Producer' a m ()
fromTBMChan chan = do
msg <- liftIO $ atomically $ readTBMChan chan
whenJust msg $ \m -> do
yield m
fromTBMChan chan
toTBMChan :: MonadIO m => TBMChan a -> Producer' a m () -> m ()
toTBMChan chan prod = runEffect $
for prod (liftIO . atomically . writeTBMChan chan)
aquirePipe :: MonadIO m =>
Maybe [(String, String)] ->
String ->
m (IO.Handle, IO.Handle, IO.Handle)
aquirePipe env' cmdStr = liftIO $ do
(Just stdin, Just stdout, Just stderr, _) <-
createProcess (shellPiped env' cmdStr)
return (stdin, stdout, stderr)
releasePipe :: MonadIO m => (IO.Handle, IO.Handle, IO.Handle) -> m ()
releasePipe (stdin, stdout, stderr) = liftIO $ do
IO.hClose stdin
IO.hClose stdout
IO.hClose stderr
shellPiped :: Maybe [(String, String)] -> String -> CreateProcess
shellPiped env' cmdStr = CreateProcess
{ cmdspec = ShellCommand cmdStr
, cwd = Nothing
, env = env'
, std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
, close_fds = False
, create_group = False
, delegate_ctlc = False
}