{-# LANGUAGE FlexibleInstances #-} module Hesh.Process ( (|>), (/>), (!>), (&>), (", "/>", "!>", "&>", ") :: (MonadIO m) => m CreateProcess -> m CreateProcess -> m a (/>) :: (MonadIO m) => m CreateProcess -> FilePath -> m a (!>) :: (MonadIO m) => m CreateProcess -> FilePath -> m a (&>) :: (MonadIO m) => m CreateProcess -> FilePath -> m a ( m CreateProcess -> FilePath -> m a instance PipeResult CreateProcess where (|>) = pipe () = redirect [Stdout] (!>) = redirect [Stderr] (&>) = redirect [Stdout, Stderr] instance PipeResult () where p1 |> p2 = passThrough (p1 |> p2) p /> path = passThrough (p /> path) p !> path = passThrough (p !> path) p &> path = passThrough (p &> path) p FilePath -> [String] -> m a instance ProcResult CreateProcess where cmd path args = return (proc path args) instance ProcResult () where cmd path args = passThrough (cmd path args) instance ProcResult String where cmd path args = stdoutToString (cmd path args) -- TODO: Use something like withCreateProcess to handle exceptions. waitForSuccess :: CreateProcess -> ProcessHandle -> IO () waitForSuccess p h = do exit <- waitForProcess h case exit of ExitSuccess -> return () ExitFailure code -> let (RawCommand command _) = cmdspec p in error ("Command " ++ command ++ " exited with failure code: " ++ show code) passThrough :: (MonadIO m) => m CreateProcess -> m () passThrough p' = do p <- p' (_, _, _, pHandle) <- liftIO (createProcess p) liftIO (waitForSuccess p pHandle) stdoutToString :: (MonadIO m) => m CreateProcess -> m String stdoutToString p' = do p <- p' (_, Just pStdout, _, pHandle) <- liftIO (createProcess p { std_out = CreatePipe }) output <- liftIO (hGetContents pStdout) liftIO (waitForSuccess p pHandle) -- Strip any trailing newline. These are almost always added to -- programs since shells don't add their own newlines, and it's a -- surprise to get these when reading a program's output. if last output == '\n' then return (init output) else return output pipe :: (MonadIO m) => m CreateProcess -> m CreateProcess -> m CreateProcess pipe p1' p2' = do p1 <- p1'; p2 <- p2' (_, Just p1Stdout, _, _) <- liftIO (createProcess p1 { std_out = CreatePipe }) -- TODO: Provide a way to fail on p1 failure (pipefail). return (p2 { std_in = UseHandle p1Stdout }) data StdHandle = Stdin | Stdout | Stderr deriving (Eq) redirect :: (MonadIO m) => [StdHandle] -> m CreateProcess -> FilePath -> m CreateProcess redirect handles p' path = do p <- p' f <- liftIO (openFile path (if handles == [Stdin] then ReadMode else WriteMode)) return (case handles of [Stdin] -> p { std_in = UseHandle f } [Stdout] -> p { std_out = UseHandle f } [Stderr] -> p { std_err = UseHandle f } [Stdout, Stderr] -> p { std_out = UseHandle f, std_err = UseHandle f }) -- TODO: Close handle(s). -- I'm not sure that I want this to stick around, so I'm not -- documenting it. If it's common enough, it's worth keeping. If not, -- it might just be confusing. (.=) :: (MonadIO m) => m String -> m String -> m Bool (.=) lhs' rhs' = do lhs <- lhs' rhs <- rhs' return (lhs == rhs)