yi-0.6.0: Yi/Process.hs
{-# LANGUAGE CPP #-}
-- Copyright (c) 2005 Don Stewart - http://www.cse.unsw.edu.au/~dons
module Yi.Process (popen, runProgCommand, runShellCommand, shellFileName,
createSubprocess, readAvailable, SubprocessInfo(..), SubprocessId) where
import System.Exit (ExitCode(ExitFailure))
import System.Directory (findExecutable)
import System.IO
import System.Process
import System.Environment ( getEnv )
import Control.Concurrent (forkIO)
import qualified Control.OldException as Control.Exception
import Foreign.Marshal.Alloc(allocaBytes)
import Foreign.C.String
import Yi.Buffer (BufferRef)
import Yi.Monad(repeatUntilM)
#ifndef mingw32_HOST_OS
import System.Posix.IO
#endif
-- | A Posix.popen compatibility mapping.
-- Based on PosixCompat, originally written by Derek Elkins for lambdabot
-- TODO: this will probably be called readProcess in the new process package (2.0)
popen :: FilePath -> [String] -> Maybe String -> IO (String,String,ExitCode)
popen file args minput =
Control.Exception.handle (\e -> return ([],show e,error (show e))) $ do
(inp,out,err,pid) <- runInteractiveProcess file args Nothing Nothing
hSetBuffering out LineBuffering
hSetBuffering err LineBuffering
case minput of
Just input -> hPutStr inp input >> hClose inp -- important!
Nothing -> return ()
-- Now, grab the input
output <- hGetContents out
errput <- hGetContents err
-- SimonM sez:
-- ... avoids blocking the main thread, but ensures that all the
-- data gets pulled as it becomes available. you have to force the
-- output strings before waiting for the process to terminate.
--
forkIO (Control.Exception.evaluate (length output) >> return ())
forkIO (Control.Exception.evaluate (length errput) >> return ())
-- And now we wait. We must wait after we read, unsurprisingly.
exitCode <- waitForProcess pid -- blocks without -threaded, you're warned.
return (output,errput,exitCode)
-- | Run a command. This looks up a program name in \$PATH, but then calls it
-- directly with the argument.
runProgCommand :: String -> [String] -> IO (String,String,ExitCode)
runProgCommand prog args = do loc <- findExecutable prog
case loc of
Nothing -> return ("","",ExitFailure 1)
Just fp -> popen fp args Nothing
------------------------------------------------------------------------
-- | Run a command using the system shell, returning stdout, stderr and exit code
shellFileName :: IO String
shellFileName = Prelude.catch (getEnv "SHELL") (const $ return "/bin/sh")
shellCommandSwitch :: String
shellCommandSwitch = "-c"
runShellCommand :: String -> IO (String,String,ExitCode)
runShellCommand cmd = do
sh <- shellFileName
popen sh [shellCommandSwitch, cmd] Nothing
--------------------------------------------------------------------------------
-- Subprocess support (ie. async processes whose output goes to a buffer)
type SubprocessId = Integer
data SubprocessInfo = SubprocessInfo {
procCmd :: FilePath,
procArgs :: [String],
procHandle :: ProcessHandle,
hIn :: Handle,
hOut :: Handle,
hErr :: Handle,
bufRef :: BufferRef,
separateStdErr :: Bool
}
{-
Simon Marlow said this:
It turns out to be dead easy to bind stderr and stdout to the same pipe. After a couple of minor tweaks the following now works:
createProcess (proc cmd args){ std_out = CreatePipe,
std_err = UseHandle stdout }
Therefore it should be possible to simplifiy the following greatly with the new process package.
-}
createSubprocess :: FilePath -> [String] -> BufferRef -> IO SubprocessInfo
createSubprocess cmd args bufref = do
#ifdef mingw32_HOST_OS
(inp,out,err,handle) <- runInteractiveProcess cmd args Nothing Nothing
let separate = True
#else
(inpReadFd,inpWriteFd) <- createPipe
(outReadFd,outWriteFd) <- createPipe
[inpRead,inpWrite,outRead,outWrite] <- mapM fdToHandle [inpReadFd,inpWriteFd,outReadFd,outWriteFd]
handle <- runProcess cmd args Nothing Nothing (Just inpRead) (Just outWrite) (Just outWrite)
let inp = inpWrite
out = outRead
err = outRead
separate = False
#endif
hSetBuffering inp NoBuffering
hSetBuffering out NoBuffering
hSetBuffering err NoBuffering
return $ SubprocessInfo { procCmd=cmd, procArgs=args, procHandle=handle, hIn=inp, hOut=out, hErr=err, bufRef=bufref, separateStdErr=separate }
-- Read as much as possible from handle without blocking
readAvailable :: Handle -> IO String
readAvailable handle = (fmap concat) $ repeatUntilM $ read_chunk handle
-- Read a chunk from a handle, bool indicates if there is potentially more data available
read_chunk :: Handle -> IO (Bool,String)
read_chunk handle = do
let bufferSize = 1024
allocaBytes bufferSize $ \buffer -> do
bytesRead <- hGetBufNonBlocking handle buffer bufferSize
s <- peekCStringLen (buffer,bytesRead)
let mightHaveMore = (bytesRead == bufferSize)
return (mightHaveMore, s)