vcswrapper-0.0.1: src/VCSWrapper/Common/Process.hs
-----------------------------------------------------------------------------
--
-- Module : VCSWrapper.Common.Process
-- Copyright : 2011 Stephan Fortelny, Harald Jagenteufel
-- License : GPL
--
-- Maintainer : stephanfortelny at gmail.com, h.jagenteufel at gmail.com
-- Stability :
-- Portability :
--
-- | Functions to execute external processes and run VCS.
--
-----------------------------------------------------------------------------
module VCSWrapper.Common.Process (
vcsExec
,vcsExecThrowingOnError
,exec
,runVcs
) where
import System.Process
import System.Exit
import System.IO (Handle, hFlush, hClose, hGetContents, hPutStr)
import Control.Concurrent
import Control.Monad.Reader
import qualified Control.Exception as Exc
import VCSWrapper.Common.Types
import Data.Maybe
{-|
Run a VCS 'Ctx' from a 'Config' and returns the result
-}
runVcs :: Config -- ^ 'Config' for a VCS
-> Ctx t -- ^ An operation running in 'Ctx'
-> IO t
runVcs config (Ctx a) = runReaderT a config
-- | Internal function to execute a VCS command. Throws an exception if the command fails.
vcsExecThrowingOnError :: String -- ^ VCS shell-command, e.g. git
-> String -- ^ VCS command, e.g. checkout
-> [String] -- ^ options
-> [(String, String)] -- ^ environment variables
-> Ctx String
vcsExecThrowingOnError vcsName cmd opts menv = do
o <- vcsExec vcsName cmd opts menv
case o of
Right out -> return out
Left exc -> Exc.throw exc
-- | Internal function to execute a VCS command
vcsExec :: String -- ^ VCS shell-command, e.g. git
-> String -- ^ VCS command, e.g. checkout
-> [String] -- ^ options
-> [(String, String)] -- ^ environment variables
-> Ctx (Either VCSException String)
vcsExec vcsName cmd opts menv = exec cmd opts menv vcsName configPath
-- | Internal function to execute a VCS command
exec :: String -- ^ VCS command, e.g. checkout
-> [String] -- ^ options
-> [(String, String)] -- ^ environment variables
-> String -- ^ VCS shell-command, e.g. git
-> (Config -> Maybe FilePath) -- ^ variable getter applied to content of Ctx
-> Ctx (Either VCSException String)
exec cmd opts menv fallBackExecutable getter = do
cfg <- ask
let args = cmd : opts
let pathToExecutable = fromMaybe fallBackExecutable (getter cfg)
(ec, out, err) <- liftIO $ readProc (configCwd cfg) pathToExecutable args menv ""
case ec of
ExitSuccess -> return $ Right out
ExitFailure i -> return $ Left $
VCSException i out err (fromMaybe "cwd not set" $ configCwd cfg ) (cmd : opts)
{- |
Same as 'System.Process.readProcessWithExitCode' but having a configurable working directory and
environment.
-}
readProc :: Maybe FilePath --working directory or Nothing if not set
-> String --command
-> [String] --arguments
-> [(String, String)] --environment can be empty
-> String --input can be empty
-> IO (ExitCode, String, String)
readProc mcwd command args menv input = do
putStrLn $ "Executing process, mcwd: "++show mcwd++", command: "++command++
",args: "++show args++",menv: "++show menv++", input"++input
(inh, outh, errh, pid) <- execProcWithPipes mcwd command args menv
outMVar <- newEmptyMVar
out <- hGetContents outh
_ <- forkIO $ Exc.evaluate (length out) >> putMVar outMVar ()
err <- hGetContents errh
_ <- forkIO $ Exc.evaluate (length err) >> putMVar outMVar ()
when (length input > 0) $ do hPutStr inh input; hFlush inh
hClose inh
takeMVar outMVar
takeMVar outMVar
hClose outh
hClose errh
ex <- waitForProcess pid
return (ex, out, err)
{- |
Setting pipes as in 'System.Process.readProcessWithExitCode' in ' but having a configurable working directory and
environment.
-}
execProcWithPipes :: Maybe FilePath -> String -> [String] -> [(String, String)]
-> IO (Handle, Handle, Handle, ProcessHandle)
execProcWithPipes mcwd command args menv = do
(Just inh, Just outh, Just errh, pid) <- createProcess (proc command args)
{ std_in = CreatePipe,
std_out = CreatePipe,
std_err = CreatePipe,
cwd = mcwd,
env = Just menv }
return (inh, outh, errh, pid)