-- Copyright 2013 Evan Laforge -- This program is distributed under the terms of the GNU General Public -- License 3.0, see COPYING or http://www.gnu.org/licenses/gpl-3.0.txt {-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -- | Utilities to deal with processes. module EL.Private.Process where import qualified Control.Concurrent as Concurrent import qualified Control.Concurrent.Async as Async import qualified Control.Concurrent.Chan as Chan import Control.Monad (forever, void) import qualified Data.String as String import qualified Data.Text as Text import qualified Data.Text.IO as Text.IO import qualified EL.Private.File as File import Global import qualified System.Exit import qualified System.IO as IO import qualified System.Process as Process data TalkOut = Stdout !Text | Stderr !Text -- | This always terminates the conversation, and effectively marks the -- channel closed. | Exit !Int deriving (Eq, Show) data TalkIn = Text !Text | EOF deriving (Eq, Show) instance String.IsString TalkIn where fromString = Text . Text.pack -- | Have a conversation with a subprocess. This doesn't use ptys, so this -- will only work if the subprocess explicitly doesn't use block buffering. conversation :: FilePath -> [String] -> Maybe [(String, String)] -> Chan.Chan TalkIn -> IO (Chan.Chan TalkOut) conversation cmd args env input = do output <- Chan.newChan Concurrent.forkIO $ Process.withCreateProcess proc $ \(Just stdin) (Just stdout) (Just stderr) pid -> do IO.hSetBuffering stdout IO.LineBuffering IO.hSetBuffering stderr IO.LineBuffering outThread <- Async.async $ void $ File.ignoreEOF $ forever $ Chan.writeChan output . Stdout =<< Text.IO.hGetLine stdout errThread <- Async.async $ void $ File.ignoreEOF $ forever $ Chan.writeChan output . Stderr =<< Text.IO.hGetLine stderr Concurrent.forkIO $ forever $ Chan.readChan input >>= \case Text t -> Text.IO.hPutStrLn stdin t >> IO.hFlush stdin EOF -> IO.hClose stdin -- Ensure both stdout and stderr are flushed before exit. Async.waitBoth outThread errThread code <- Process.waitForProcess pid Chan.writeChan output $ Exit $ case code of System.Exit.ExitFailure code -> code System.Exit.ExitSuccess -> 0 return output where proc = (Process.proc cmd args) { Process.std_in = Process.CreatePipe , Process.std_out = Process.CreatePipe , Process.std_err = Process.CreatePipe , Process.close_fds = True , Process.env = env }