packages feed

process-progress (empty) → 0.9

raw patch · 11 files changed

+1073/−0 lines, 11 filesdep +HUnitdep +ListLikedep +basesetup-changed

Dependencies added: HUnit, ListLike, base, bytestring, deepseq, mtl, process, process-listlike, text, time, unix, utf8-string

Files

+ Setup.hs view
@@ -0,0 +1,20 @@+import Distribution.Simple+import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(buildDir))+import Distribution.Simple.Program+import System.Cmd+import System.Directory+import System.Exit++main = copyFile "debian/changelog" "changelog" >>+       defaultMainWithHooks simpleUserHooks {+         postBuild =+             \ _ _ _ lbi ->+                 case buildDir lbi of+                   "dist-ghc/build" -> return ()+                   _ -> runTestScript lbi+       , runTests = \ _ _ _ lbi -> runTestScript lbi+       }++runTestScript lbi =+    system (buildDir lbi ++ "/tests/tests") >>= \ code ->+    if code == ExitSuccess then return () else error "unit test failure"
+ System/Posix/EnvPlus.hs view
@@ -0,0 +1,11 @@+module System.Posix.EnvPlus+    ( module System.Posix.Env+    , modifyEnv+    ) where++import System.Posix.Env++-- | Generalization of Posix setEnv/unSetEnv.+modifyEnv :: String -> (Maybe String -> Maybe String) -> IO ()+modifyEnv name f =+    getEnv name >>= maybe (unsetEnv name) (\ x -> setEnv name x True) . f
+ System/Process/Progress.hs view
@@ -0,0 +1,17 @@+-- | `Functions to run a process and manage the type and amount of output+-- it generates.+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module System.Process.Progress+    ( module System.Process.Read.Chunks+    , module System.Process.Read.Compat+    , module System.Process.Read.Monad+    , module System.Process.Read.Verbosity+    , module System.Process.Read.Convenience+    ) where++import System.Process.Read.Chunks+import System.Process.Read.Convenience+import System.Process.Read.Compat+import System.Process.Read.Monad+import System.Process.Read.Verbosity
+ System/Process/Read/Chunks.hs view
@@ -0,0 +1,294 @@+-- | The 'readProcessChunks' function is a process reader that returns+-- a list (stream) of 'Output', which represent chunks of text read+-- from 'Stdout', 'Stderr', a 'Result' code, or an 'Exception'.  This+-- has the advantage of preserving the order in which these things+-- appeared.  The 'foldOutput', 'foldOutputsL', and 'foldOutputsR'+-- functions can be used to process the output stream.  The output+-- text can be any 'NonBlocking' instance, which needs to be implemented+-- using a non-blocking read function like 'B.hGetSome'.++{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeSynonymInstances #-}+module System.Process.Read.Chunks (+  NonBlocking(..),+  Output(..),+  foldOutput,+  foldOutputsL,+  foldOutputsR,+  readProcessChunks,+  readProcessChunks'+  ) where++import Control.Applicative ((<$>))+import Control.Concurrent (forkIO, threadDelay, MVar, newEmptyMVar, putMVar, takeMVar)+import Control.DeepSeq (NFData)+import Control.Exception as E (onException, catch, mask, try, throwIO, SomeException)+import Control.Monad (unless)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.ListLike (ListLike(..), ListLikeIO(..))+import Data.Word (Word8)+import qualified GHC.IO.Exception as E+import GHC.IO.Exception (IOErrorType(ResourceVanished), IOException(ioe_type))+import Prelude hiding (null, length, rem)+import qualified Prelude+import System.Exit (ExitCode)+import System.IO hiding (hPutStr, hGetContents)+import System.IO.Error (mkIOError)+import System.Process.Read (ListLikePlus(..))+import System.IO.Unsafe (unsafeInterleaveIO)+import System.Process (CreateProcess(..), StdStream(CreatePipe), ProcessHandle,+                       createProcess, waitForProcess, terminateProcess)++-- | This lets us use deepseq's force on the stream of data returned+-- by the process-progress functions.+instance NFData ExitCode++-- | Class of types which can also be used by 'System.Process.Read.readProcessChunks'.+class ListLikePlus a c => NonBlocking a c where+  hGetSome :: Handle -> LengthType a -> IO a+  toChunks :: a -> [a]++data Output a = Stdout a | Stderr a | Result ExitCode | Exception IOError deriving (Eq, Show)++-- | A fold function for the Output type, dispatches the value+-- depending on the constructor.+foldOutput :: ListLikePlus a c =>+              (ExitCode -> b)+           -> (a -> b)+           -> (a -> b)+           -> (IOError -> b)+           -> Output a+           -> b+foldOutput codefn _ _ _ (Result code) = codefn code+foldOutput _ outfn _ _ (Stdout out) = outfn out+foldOutput _ _ errfn _ (Stderr err) = errfn err+foldOutput _ _ _ exnfn (Exception exn) = exnfn exn++foldOutputsL :: ListLikePlus a c =>+                (b -> ExitCode -> b)+             -> (b -> a -> b)+             -> (b -> a -> b)+             -> (b -> IOError -> b)+             -> b+             -> [Output a]+             -> b+foldOutputsL _ _ _ _ result [] = result+foldOutputsL codefn outfn errfn exnfn result (x : xs) =+    let result' = foldOutput (codefn result) (outfn result) (errfn result) (exnfn result) x in+    foldOutputsL codefn outfn errfn exnfn result' xs+    -- Pretty sure this is equivalent:+    -- foldl (\ r x -> foldOutput (codefn r) (outfn r) (errfn r) (exnfn r) x) result xs++foldOutputsR :: forall a b c. ListLikePlus a c =>+                (b -> ExitCode -> b)+             -> (b -> a -> b)+             -> (b -> a -> b)+             -> (b -> IOError -> b)+             -> b+             -> [Output a]+             -> b+foldOutputsR _ _ _ _ result [] = result+foldOutputsR codefn outfn errfn exnfn result (x : xs) =+    let result' = foldOutputsR codefn outfn errfn exnfn result xs in+    foldOutput (codefn result') (outfn result') (errfn result') (exnfn result') x+    -- foldr (\ x r -> foldOutput (codefn r) (outfn r) (errfn r) (exnfn r) x) result xs++-- | This is a process runner which (at the cost of a busy loop) gives+-- you the chunks of text read from stdout and stderr interleaved in+-- the order they were written, along with any ResourceVanished+-- exxception that might occur.  Its interface is similar to+-- 'System.Process.Read.readModifiedProcessWith', though the+-- implementation is somewhat alarming.+readProcessChunks :: (NonBlocking a c) =>+                     CreateProcess+                  -> a+                  -> IO [Output a]+readProcessChunks p input = mask $ \ restore -> do+  (Just inh, Just outh, Just errh, pid) <-+      createProcess (p {std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe })++  binary input [inh, outh, errh]++  flip onException+    (do hClose inh; hClose outh; hClose errh;+        terminateProcess pid; waitForProcess pid) $ restore $ do++    waitOut <- forkWait $ elements pid (Just outh) (Just errh)++    -- now write and flush any input+    (do unless (null input) (hPutStr inh input >> hFlush inh)+        hClose inh) `E.catch` resourceVanished (\ _e -> return ())++    -- wait on the output+    waitOut++-- | Take the info returned by 'createProcess' and gather and return+-- the stdout and stderr of the process.+elements :: NonBlocking a c => ProcessHandle -> Maybe Handle -> Maybe Handle -> IO [Output a]+elements pid outh errh =+    do case (outh, errh) of+         (Nothing, Nothing) ->+           -- EOF on both output descriptors, get exit code.  It can be+           -- argued that the result will always contain exactly one exit+           -- code if traversed to its end, because the only case of+           -- 'elements' that does not recurse is the one that adds a Result,+           -- and there is nowhere else where a Result is added.  However,+           -- the process doing the traversing may die before that end is+           -- reached.+           do result <- waitForProcess pid+              return [Result result]+         _ ->+           -- The available output has been processed, send input and read+           -- from the ready handles+           do (outh', errh', elems') <- ready uSecs outh errh+              more <- unsafeInterleaveIO (elements pid outh' errh')+              return $ elems' ++ more++data Readyness = Ready | Unready | Closed++hReady' :: Handle -> IO Readyness+hReady' h =+    -- This check is necessary because if we run hReady on a closed+    -- handle it will keep giving us EOF exceptions.+    hIsClosed h >>= checkReady+    where+      checkReady True = return Closed+      checkReady False =+          (hReady h >>= (\ flag -> return (if flag then Ready else Unready)))+            `catch` endOfFile (\ _ -> return Closed)++-- | Wait until at least one handle is ready and then read output.  If+-- none of the output descriptors are ready for reading the function+-- sleeps and tries again.+ready :: (NonBlocking a c) =>+         Int -> Maybe Handle -> Maybe Handle+      -> IO (Maybe Handle, Maybe Handle, [Output a])+ready waitUSecs outh errh =+    do+      outReady <- maybe (return Closed) hReady' outh+      errReady <- maybe (return Closed) hReady' errh+      case (outReady, errReady) of+        (Closed, Closed) ->+            return (Nothing, Nothing, [])+        -- Input handle closed and there are no ready output handles,+        -- wait a bit+        (Unready, Unready) ->+            do threadDelay waitUSecs+               ready (min maxUSecs (2 * waitUSecs)) outh errh+        _ ->+            -- One or both output handles are ready, try to read from+            -- them.  If (out1 ++ out2) is an empty list we know that+            -- we have reached EOF on both descriptors, because at+            -- least one was ready and nextOut only returns [] for a+            -- ready file descriptor on EOF.+            do (errh', out1) <- nextOut errh errReady Stderr+               (outh', out2) <- nextOut outh outReady Stdout+               return (outh', errh', out1 ++ out2)++-- | Return the next output element and the updated handle from a+-- handle which is assumed ready.  If the handle is closed or unready,+-- or we reach end of file an empty list of outputs is returned.+nextOut :: NonBlocking a c => Maybe Handle -> Readyness -> (a -> Output a) -> IO (Maybe Handle, [Output a])+nextOut Nothing _ _ = return (Nothing, [])+nextOut (Just h) Ready constructor =	-- Perform a read+   do -- We can call hGetSome because we know this handle is ready for reading.+      a <- hGetSome h (fromIntegral bufSize)+      case length a of+        -- A zero length read, unlike a zero length write, always+        -- means EOF.+        0 -> do hClose h+                return (Nothing, [])+        -- Got some output+        _n -> return (Just h, [constructor a])+nextOut h _ _ = return (h, [])	-- Handle is closed or not ready++endOfFile :: (IOError -> IO a) -> IOError -> IO a+endOfFile eeof e = if E.ioe_type e == E.EOF then eeof e else ioError e++bufSize :: Int+bufSize = 4096		-- maximum chunk size+uSecs :: Int+uSecs = 8		-- minimum wait time, doubles each time nothing is ready+maxUSecs :: Int+maxUSecs = 100000	-- maximum wait time (microseconds)++-- | A test version of readProcessChunks.+-- Pipes code here: http://hpaste.org/76631+readProcessChunks' :: (NonBlocking a c) =>+                      CreateProcess+                   -> a+                   -> IO [Output a]+readProcessChunks' p input = mask $ \ restore -> do+  (Just inh, Just outh, Just errh, pid) <-+      createProcess (p {std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe })++  binary input [inh, outh, errh]++  flip onException+    (do hClose inh; hClose outh; hClose errh;+        terminateProcess pid; waitForProcess pid) $ restore $ do++    waitOut <- forkWait $ elements' pid outh errh++    -- now write and flush any input+    (do unless (null input) (hPutStr inh input >> hFlush inh)+        hClose inh) `catch` resourceVanished (\ _e -> return ())++    -- wait on the output+    waitOut++-- | An idea for a replacement of elements+elements' :: forall a c. NonBlocking a c => ProcessHandle -> Handle -> Handle -> IO [Output a]+elements' pid outh errh = do+  res <- newEmptyMVar+  -- We use Exception EOF to indicate that we reached EOF on one+  -- handle.  This is not a value that could otherwise have been put+  -- into the MVar.+  _outtid <- forkIO $ hGetContents outh >>= mapM_ (\ c -> putMVar res (Stdout c)) . toChunks >> hClose outh >> putMVar res (Exception (mkIOError E.EOF "EOF" Nothing Nothing))+  _errtid <- forkIO $ hGetContents errh >>= mapM_ (\ c -> putMVar res (Stderr c)) . toChunks >> hClose errh >> putMVar res (Exception (mkIOError E.EOF "EOF" Nothing Nothing))+  takeChunks 0 res+    where+      takeChunks :: Int -> MVar (Output a) -> IO [Output a]+      takeChunks 2 _ = waitForProcess pid >>= \ result -> return [Result result]+      takeChunks closedCount res = takeMVar res >>= takeChunk closedCount res+      takeChunk closedCount res (Exception _) = takeChunks (closedCount + 1) res+      takeChunk closedCount res x =+          do xs <- unsafeInterleaveIO $ takeChunks closedCount res+             return (x : xs)++forkWait :: IO a -> IO (IO a)+forkWait a = do+  res <- newEmptyMVar+  _ <- mask $ \restore -> forkIO $ try (restore a) >>= putMVar res+  return (takeMVar res >>= either (\ex -> throwIO (ex :: SomeException)) return)++-- | Wrapper for a process that provides a handler for the+-- ResourceVanished exception.  This is frequently an exception we+-- wish to ignore, because many processes will deliberately exit+-- before they have read all of their input.+resourceVanished :: (IOError -> IO a) -> IOError -> IO a+resourceVanished epipe e = if ioe_type e == ResourceVanished then epipe e else ioError e++{-+proc' :: CmdSpec -> CreateProcess+proc' (RawCommand cmd args) = proc cmd args+proc' (ShellCommand cmd) = shell cmd+-}++{- Its not safe to read a bytestring chunk and then convert it to a string, the chunk+   might end with part of a character.+instance NonBlocking String Char where+  -- hGetNonBlocking n h = (toString . B.concat . L.toChunks) <$> L.hGetNonBlocking n h+  hGetSome h n = toList <$> B.hGetSome h n+  toChunks = error "toChunks"+-}++instance NonBlocking B.ByteString Word8 where+  -- hGetNonBlocking = B.hGetNonBlocking+  hGetSome = B.hGetSome+  toChunks = (: [])++instance NonBlocking L.ByteString Word8 where+  -- hGetNonBlocking = L.hGetNonBlocking+  hGetSome h n = (L.fromChunks . (: [])) <$> B.hGetSome h (fromIntegral n)+  toChunks = Prelude.map (L.fromChunks . (: [])) . L.toChunks
+ System/Process/Read/Compat.hs view
@@ -0,0 +1,37 @@+-- | Some functions brought over from my obsolete progress packages.+{-# LANGUAGE RankNTypes, ScopedTypeVariables, TypeFamilies #-}+module System.Process.Read.Compat+    ( echo+    , oneResult+    , timeTask+    ) where++import Control.Exception (evaluate)+import Data.Time (NominalDiffTime, getCurrentTime, diffUTCTime)+import System.Exit (ExitCode(..))+import System.Process (CmdSpec(..), showCommandForUser)+import System.Process.Read.Chars (ListLikePlus)+import System.Process.Read.Convenience (ePutStrLn, keepResult)+import System.Process.Read.Chunks (Output(..))++-- | Output a description of a command and then run it.+echo :: CmdSpec -> IO () -> IO ()+echo (RawCommand cmd args) io = ePutStrLn ("-> " ++ showCommandForUser cmd args) >> io+echo (ShellCommand cmd) io = ePutStrLn ("-> " ++ cmd) >> io++-- | Extract the result code of an output stream, throw an error if+-- there isn't exactly one of them.+oneResult :: ListLikePlus a c => [Output a] -> ExitCode+oneResult xs =+    case keepResult xs of+      [code] -> code+      [] -> error "Missing result code"+      codes -> error $ "Multiple result codes: " ++ show codes++-- | Run a task and return the elapsed time along with its result.+timeTask :: IO a -> IO (a, NominalDiffTime)+timeTask x =+    do start <- getCurrentTime+       result <- x >>= evaluate+       finish <- getCurrentTime+       return (result, diffUTCTime finish start)
+ System/Process/Read/Convenience.hs view
@@ -0,0 +1,245 @@+{-# LANGUAGE FlexibleContexts, RankNTypes, ScopedTypeVariables, TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+module System.Process.Read.Convenience+    ( -- * Predicates+      isResult+    , isStdout+    , isStderr+    , isOutput+    , isException+    -- * Filters+    , discardStdout+    , discardStderr+    , discardOutput+    , discardExceptions+    , discardResult+    , keepStdout+    , keepStderr+    , keepOutput+    , keepExceptions+    , keepResult+    -- * Transformers+    , mergeToStdout+    , mergeToStderr+    , mapMaybeResult+    , mapMaybeStdout+    , mapMaybeStderr+    , mapMaybeException+    -- * Collectors+    , collectOutputs+    -- * IO operations+    , ePutStr+    , ePutStrLn+    , eMessage+    , eMessageLn++    , foldException+    , foldChars+    , foldStdout+    , foldStderr+    , foldResult+    , foldSuccess+    , foldFailure+    , foldFailure'++    , doException+    , doOutput+    , doStdout+    , doStderr+    , doExit+    , doAll++    , dots+    , prefixed+    ) where++import Control.Exception (throw)+import Control.Monad (when)+import Control.Monad.Trans (MonadIO, liftIO)+import Data.ListLike (ListLike(empty, append, concat, span, tail, singleton, null), ListLikeIO(hPutStr))+import Data.Maybe (mapMaybe)+import Prelude hiding (length, rem, concat, span, tail, null)+import System.Exit (ExitCode(..), exitWith)+import System.IO (stdout, stderr)+import qualified System.IO as IO (hPutStr, hPutStrLn)+import System.Process.Read.Chars (ListLikePlus(..))+import System.Process.Read.Chunks (NonBlocking(..), Output(..), foldOutput, foldOutputsR)+import System.Process.Read.Instances ()++isResult :: ListLikePlus a c => Output a -> Bool+isResult = foldOutput (const True) (const False) (const False) (const False)+isStdout :: ListLikePlus a c => Output a -> Bool+isStdout = foldOutput (const False) (const True) (const False) (const False)+isStderr :: ListLikePlus a c => Output a -> Bool+isStderr = foldOutput (const False) (const False) (const True) (const False)+isOutput :: ListLikePlus a c => Output a -> Bool+isOutput = foldOutput (const False) (const True) (const True) (const False)+isException :: ListLikePlus a c => Output a -> Bool+isException = foldOutput (const False) (const False) (const False) (const True)++toStdout :: ListLikePlus a c => Output a -> Output a+toStdout = foldOutput Result Stdout Stdout Exception+toStderr :: ListLikePlus a c => Output a -> Output a+toStderr = foldOutput Result Stderr Stderr Exception++mergeToStdout :: ListLikePlus a c => [Output a] -> [Output a]+mergeToStdout = map toStdout+mergeToStderr :: ListLikePlus a c => [Output a] -> [Output a]+mergeToStderr = map toStderr++discardStdout :: ListLikePlus a c => [Output a] -> [Output a]+discardStdout = filter (not . isStdout)+discardStderr :: ListLikePlus a c => [Output a] -> [Output a]+discardStderr = filter (not . isStderr)+discardOutput :: ListLikePlus a c => [Output a] -> [Output a]+discardOutput = filter (\ x -> not (isStdout x || isStderr x))+discardExceptions :: ListLikePlus a c => [Output a] -> [Output a]+discardExceptions = filter (not . isException)+discardResult :: ListLikePlus a c => [Output a] -> [Output a]+discardResult = filter (not . isResult)++keepStdout :: ListLikePlus a c => [Output a] -> [a]+keepStdout = mapMaybe $ foldOutput (const Nothing) Just (const Nothing) (const Nothing)+keepStderr :: ListLikePlus a c => [Output a] -> [a]+keepStderr = mapMaybe $ foldOutput (const Nothing) (const Nothing) Just (const Nothing)+keepOutput :: ListLikePlus a c => [Output a] -> [a]+keepOutput = mapMaybe $ foldOutput (const Nothing) Just Just (const Nothing)+keepExceptions :: ListLikePlus a c => [Output a] -> [IOError]+keepExceptions = mapMaybe $ foldOutput (const Nothing) (const Nothing) (const Nothing) Just+keepResult :: ListLikePlus a c => [Output a] -> [ExitCode]+keepResult = mapMaybe $ foldOutput Just (const Nothing) (const Nothing) (const Nothing)++mapMaybeResult :: ListLikePlus a c => (ExitCode -> Maybe (Output a)) -> [Output a] -> [Output a]+mapMaybeResult f = mapMaybe (foldOutput f (Just . Stdout) (Just . Stderr) (Just . Exception))+mapMaybeStdout :: ListLikePlus a c => (a -> Maybe (Output a)) -> [Output a] -> [Output a]+mapMaybeStdout f = mapMaybe (foldOutput (Just . Result) f (Just . Stderr) (Just . Exception))+mapMaybeStderr :: ListLikePlus a c => (a -> Maybe (Output a)) -> [Output a] -> [Output a]+mapMaybeStderr f = mapMaybe (foldOutput (Just . Result) (Just . Stdout) f (Just . Exception))+mapMaybeException :: ListLikePlus a c => (IOError -> Maybe (Output a)) -> [Output a] -> [Output a]+mapMaybeException f = mapMaybe (foldOutput (Just . Result) (Just . Stdout) (Just . Stderr) f)++collectOutputs :: forall a c. ListLikePlus a c => [Output a] -> ([ExitCode], a, a, [IOError])+collectOutputs xs =+    foldOutputsR codefn outfn errfn exnfn result0 xs+    where+      result0 :: ([ExitCode], a, a, [IOError])+      result0 = ([], empty, empty, [])+      codefn :: ([ExitCode], a, a, [IOError]) -> ExitCode -> ([ExitCode], a, a, [IOError])+      codefn (codes, outs, errs, exns) code = (code : codes, outs, errs, exns)+      outfn :: ([ExitCode], a, a, [IOError]) -> a -> ([ExitCode], a, a, [IOError])+      outfn (codes, outs, errs, exns) out = (codes, append out outs, errs, exns)+      errfn :: ([ExitCode], a, a, [IOError]) -> a -> ([ExitCode], a, a, [IOError])+      errfn (codes, outs, errs, exns) err = (codes, outs, append err errs, exns)+      exnfn :: ([ExitCode], a, a, [IOError]) -> IOError -> ([ExitCode], a, a, [IOError])+      exnfn (codes, outs, errs, exns) exn = (codes, outs, errs, exn : exns)++ePutStr :: MonadIO m => String -> m ()+ePutStr s = liftIO $ IO.hPutStr stderr s++ePutStrLn :: MonadIO m => String -> m ()+ePutStrLn s = liftIO $ IO.hPutStrLn stderr s++eMessage :: MonadIO m => String -> a -> m a+eMessage s x = ePutStr s >> return x++eMessageLn :: MonadIO m => String -> a -> m a+eMessageLn s x = ePutStrLn s >> return x++foldException :: ListLikePlus a c => (IOError -> IO (Output a)) -> [Output a] -> IO [Output a]+foldException exnfn = mapM (foldOutput (return . Result) (return . Stdout) (return . Stderr) exnfn)++foldChars :: ListLikePlus a c => (a -> IO (Output a)) -> (a -> IO (Output a)) -> [Output a] -> IO [Output a]+foldChars outfn errfn = mapM (foldOutput (return . Result) outfn errfn (return . Exception))++foldStdout :: ListLikePlus a c => (a -> IO (Output a)) -> [Output a] -> IO [Output a]+foldStdout outfn = foldChars outfn (return . Stderr)++foldStderr :: ListLikePlus a c => (a -> IO (Output a)) -> [Output a] -> IO [Output a]+foldStderr errfn = foldChars (return . Stdout) errfn++foldResult :: ListLikePlus a c => (ExitCode -> IO (Output a)) -> [Output a] -> IO [Output a]+foldResult codefn = mapM (foldOutput codefn (return . Stdout) (return . Stderr) (return . Exception))++foldFailure :: ListLikePlus a c => (Int -> IO (Output a)) -> [Output a] -> IO [Output a]+foldFailure failfn = foldResult codefn+    where codefn (ExitFailure n) = failfn n+          codefn x = return (Result x)++foldResult' :: ListLikePlus a c => ([Output a] -> ExitCode -> IO (Output a)) -> [Output a] -> IO [Output a]+foldResult' codefn outputs = mapM (foldOutput (codefn outputs) (return . Stdout) (return . Stderr) (return . Exception)) outputs++foldFailure' :: ListLikePlus a c => (Int -> IO (Output a)) -> [Output a] -> IO [Output a]+foldFailure' failfn outputs = foldResult' codefn outputs+    where codefn outputs (ExitFailure n) = failfn n+          codefn _ x = return (Result x)++foldSuccess :: ListLikePlus a c => IO (Output a) -> [Output a] -> IO [Output a]+foldSuccess successfn = foldResult codefn+    where codefn ExitSuccess = successfn+          codefn x = return (Result x)++doException :: ListLikePlus a c => [Output a] -> IO [Output a]+doException = foldException throw++doOutput :: ListLikePlus a c => [Output a] -> IO [Output a]+doOutput = foldChars (\ cs -> hPutStr stdout cs >> return (Stdout cs)) (\ cs -> hPutStr stderr cs >> return (Stderr cs))++doStdout :: ListLikePlus a c => [Output a] -> IO [Output a]+doStdout = foldStdout (\ cs -> hPutStr stdout cs >> return (Stdout cs))++doStderr :: ListLikePlus a c => [Output a] -> IO [Output a]+doStderr = foldStderr (\ cs -> hPutStr stderr cs >> return (Stderr cs))++-- | I don't see much use for this.+doExit :: ListLikePlus a c => [Output a] -> IO [Output a]+doExit = foldResult (\ code -> exitWith code >> return (Result code))++doAll :: ListLikePlus a c => [Output a] -> IO [Output a]+doAll = mapM (foldOutput (\ code -> exitWith code >> return (Result code))+                         (\ cs -> hPutStr stdout cs >> return (Stdout cs))+                         (\ cs -> hPutStr stderr cs >> return (Stderr cs))+                         throw)++dots :: forall a c. NonBlocking a c => LengthType a -> (LengthType a -> IO ()) -> [Output a] -> IO [Output a]+dots charsPerDot nDots outputs =+    nDots 1 >> dots' 0 outputs >>= eMessage "\n"+    where+      dots' _ [] = nDots 1 >> return []+      dots' rem (x : xs) =+          do let (count', rem') = divMod (rem + foldOutput (const 0) length' length' (const 0) x) charsPerDot+             when (count' > 0) (nDots count')+             xs' <- dots' rem' xs+             return (x : xs')++-- | Output the stream with a prefix added at the beginning of each+-- line of stdout and stderr.+prefixed :: forall a c. (Enum c, ListLikePlus a c) => a -> a -> [Output a] -> IO [Output a]+prefixed opre epre output =+    mapM (\ (old, new) -> doOutput [new] >> return old) (prefixes opre epre output)++-- | Return the original stream of outputs zipped with one that has+-- had prefixes for stdout and stderr inserted.  For the prefixed+-- stream only, apply @map snd@.+prefixes :: forall a c. (Enum c, ListLikePlus a c) => a -> a -> [Output a] -> [(Output a, Output a)]+prefixes opre epre output =+    loop True output+    where+      loop :: (Enum c, ListLike a c) => Bool -> [Output a] -> [(Output a, Output a)]+      loop _ [] = []+      loop bol (x@(Stdout s) : xs) = let (s', bol') = step bol opre s in (x, Stdout s') : loop bol' xs+      loop bol (x@(Stderr s) : xs) = let (s', bol') = step bol epre s in (x, Stderr s') : loop bol' xs+      loop bol (x : xs) = (x, Stdout empty) : loop bol xs++      step :: (Enum c, ListLike a c) => Bool -> a -> a -> (a, Bool)+      step bol pre s =+          let (a, b) = Data.ListLike.span (\ c -> fromEnum c /= fromEnum '\n') s in+          if null a+          then if null b+               then (empty, bol)+               else let x = (if bol then pre else empty)+                        (s', bol') = step True pre (tail b) in+                    (concat [x, singleton (toEnum . fromEnum $ '\n'), s'], bol')+          -- There is some text before a possible newline+          else let x = (if bol then append pre a else a)+                   (s', bol') = step False pre b in+               (append x s', bol')
+ System/Process/Read/Monad.hs view
@@ -0,0 +1,171 @@+-- | A perhaps over-engineered set of wrappers around+-- readProcessChunks to run processes with a variety of echoing+-- options and responses to failure.+{-# LANGUAGE FlexibleContexts, FlexibleInstances, RankNTypes, ScopedTypeVariables, TypeFamilies, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+module System.Process.Read.Monad+    ( -- * Run processes with various types and amounts of feedback+      runProcessS+    , runProcessQ+    , runProcessD+    , runProcessV+    , runProcessSF+    , runProcessQF+    , runProcessDF+    , runProcessVF+    , runProcessSE+    , runProcessQE+    , runProcessDE+    ) where++import Control.Monad (when, unless)+import Control.Monad.State (StateT(runStateT), get, put)+import Control.Monad.Trans (MonadIO, liftIO)+import qualified Data.ListLike as P+import Prelude hiding (print)+import System.Exit (ExitCode(ExitFailure))+import System.IO (hPutStrLn, hPutStr, stderr)+import System.Process (CreateProcess(cmdspec), CmdSpec(RawCommand, ShellCommand), showCommandForUser)+import qualified System.Process.Read.Chars as P+import qualified System.Process.Read.Chunks as P+import qualified System.Process.Read.Convenience as P++-- | The state we need when running processes+data RunState s+    = RunState+      { cpd :: Int  -- ^ Output one dot per n characters of process output, 0 means no dots+      , trace :: Bool -- ^ Echo the command line before starting, and after with the result code+      , echo :: Bool -- ^ Echo the output as it is red, using the prefixes set below+      , prefixes :: Maybe (s, s)+      -- ^ Prepend a prefix to the echoed lines of stdout and stderr.+      --  Special case for Just ("", ""), which means echo unmodified output.+      , exnPrefixes :: Maybe (s, s)+      -- ^ Prefixes to use when generating output after an exception occurs.+      , failEcho :: Bool -- ^ Echo the process output if the result code is ExitFailure+      , failExit :: Bool -- ^ Throw an IOError if the result code is ExitFailure+      } deriving (Show)++defaultRunState :: RunState s+defaultRunState = RunState {cpd=0, trace=True, echo=False, failEcho=False, failExit=False, prefixes=Nothing, exnPrefixes=Nothing}++-- | The monad for running processes+type RunT s = StateT (RunState s)++withRunState :: MonadIO m => RunState s -> RunT s m a -> m a+withRunState s action =+    (runStateT action) s >>= return . fst++modifyRunState :: MonadIO m => (RunState s -> RunState s) -> RunT s m ()+modifyRunState modify = get >>= put . modify++charsPerDot :: MonadIO m => Int -> RunT s m ()+charsPerDot x = modifyRunState (\ s -> s {cpd = x})++echoCommand :: MonadIO m => Bool -> RunT s m ()+echoCommand x = modifyRunState (\ s -> s {trace = x})++echoOnFailure :: MonadIO m => Bool -> RunT s m ()+echoOnFailure x = modifyRunState (\ s -> s {failEcho = x})++exceptionOnFailure :: MonadIO m => Bool -> RunT s m ()+exceptionOnFailure x = modifyRunState (\ s -> s {failExit = x})++echoOutput :: MonadIO m => Bool -> RunT s m ()+echoOutput x = modifyRunState (\ s -> s {echo = x})++setPrefixes :: (P.ListLikePlus s c, MonadIO m) => Maybe (s, s) -> RunT s m ()+setPrefixes x = modifyRunState (\ s -> s {prefixes = x})++runProcessM :: forall s c m. (P.NonBlocking s c, Enum c, MonadIO m) => CreateProcess -> s -> RunT s m [P.Output s]+runProcessM p input =+    do s <- get+       liftIO $ do+         when (trace s) (hPutStrLn stderr ("-> " ++ showCommand (cmdspec p)))+         (out1 :: [P.Output s]) <- P.readProcessChunks p input+         (out2 :: [P.Output s]) <- if cpd s > 0 then P.dots (fromIntegral (cpd s)) (\ n -> hPutStr stderr (replicate (fromIntegral n) '.')) out1 else return out1+         (out3 :: [P.Output s]) <- if echo s then doOutput (prefixes s) out2 else return out2+         (out5 :: [P.Output s]) <- if failExit s then P.foldFailure' (\ n -> doOutput (exnPrefixes s) out3 >> error (showCommand (cmdspec p) ++ " -> ExitFailure " ++ show n)) out3 else return out3+         (out6 :: [P.Output s]) <- (if trace s then  P.foldResult (\ ec -> hPutStrLn stderr ("<- " ++ showCommand (cmdspec p) ++ ": " ++ show ec) >> return (P.Result ec)) else return) out5+         (out7 :: [P.Output s]) <- (if failEcho s then P.foldFailure (\ n -> unless (trace s) (hPutStrLn stderr ("<- " ++ showCommand (cmdspec p) ++ ": " ++ show (ExitFailure n))) >>+                                                                              doOutput (prefixes s) out5 >> return (P.Result (ExitFailure n))) else return) out6+         return out7++doOutput :: (P.ListLikePlus a c, Enum c) => Maybe (a, a) -> [P.Output a] -> IO [P.Output a]+doOutput prefixes out = maybe (P.doOutput out) (\ (sout, serr) -> P.prefixed sout serr out) prefixes >> return out+-- doOutput prefixes out = P.doOutput out >> return out++s :: MonadIO m => RunT s m ()+s = echoCommand False++c :: MonadIO m => RunT s m ()+c = echoCommand True++v :: (Enum c, P.ListLikePlus s c, MonadIO m) => RunT s m ()+v = echoOutput True >> setPrefixes (Just (P.fromList (map (toEnum . fromEnum) " 1> "), P.fromList (map (toEnum . fromEnum) " 2> "))) >> echoOnFailure False++d :: MonadIO m => RunT s m ()+d = charsPerDot 50 >> echoOutput False++f :: MonadIO m => RunT s m ()+f = exceptionOnFailure True++e :: (Enum c, P.ListLikePlus s c, MonadIO m) => RunT s m ()+e = echoOnFailure True >> setPrefixes (Just (P.fromList (map (toEnum . fromEnum) " 1> "), P.fromList (map (toEnum . fromEnum) " 2> "))) >> exceptionOnFailure True >> echoOutput False++-- | Run silently+runProcessS :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessS p input = withRunState defaultRunState (s >> runProcessM p input)++-- | Command line trace only.+runProcessQ :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessQ p input = withRunState defaultRunState (runProcessM p input)++-- | Dot output+runProcessD :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessD p input =+    withRunState defaultRunState (c >> d >> runProcessM p input)++-- | Echo output+runProcessV :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessV p input =+    withRunState defaultRunState (c >> v >> runProcessM p input)++-- | Exception on failure+runProcessSF :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessSF p input =+    withRunState defaultRunState (s >> f >> runProcessM p input)++runProcessQF :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessQF p input =+    withRunState defaultRunState (c >> f >> runProcessM p input)++-- | Dot output and exception on failure+runProcessDF :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessDF p input =+    withRunState defaultRunState (c >> d >> f >> runProcessM p input)++-- | Echo output and exception on failure+runProcessVF :: (P.NonBlocking a c, Enum c, MonadIO m) => CreateProcess -> a -> m [P.Output a]+runProcessVF p input =+    withRunState defaultRunState (c >> v >> f >> runProcessM p input)++-- | Exception and echo on failure+runProcessSE :: (P.NonBlocking a c, Enum c, MonadIO m) => Maybe (a, a) -> CreateProcess -> a -> m [P.Output a]+runProcessSE prefixes p input =+    withRunState (defaultRunState {exnPrefixes = prefixes}) (s >> e >> runProcessM p input)++-- | Exception and echo on failure+runProcessQE :: (P.NonBlocking a c, Enum c, MonadIO m) => Maybe (a, a) -> CreateProcess -> a -> m [P.Output a]+runProcessQE prefixes p input =+    withRunState (defaultRunState {exnPrefixes = prefixes}) (c >> e >> runProcessM p input)++-- | Dot output, exception on failure, echo on failure.  Note that+-- runProcessVE isn't a useful option, you get the output twice.  VF+-- makes more sense.+runProcessDE :: (P.NonBlocking a c, Enum c, MonadIO m) => Maybe (a, a) -> CreateProcess -> a -> m [P.Output a]+runProcessDE prefixes p input =+    withRunState (defaultRunState {exnPrefixes = prefixes}) (c >> d >> e >> runProcessM p input)++showCommand :: CmdSpec -> String+showCommand (RawCommand cmd args) = showCommandForUser cmd args+showCommand (ShellCommand cmd) = cmd
+ System/Process/Read/Verbosity.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE FlexibleContexts, TypeFamilies #-}+module System.Process.Read.Verbosity+    ( quieter+    , noisier+    , withModifiedVerbosity+    , defaultVerbosity+    , verbosity+    -- * Process functions controlled by the VERBOSITY level.+    , runProcess+    , runProcessF+    -- * Output functions controlled by the VERBOSITY level.  We want these+    -- to output whenever the runProcess functions are not silent, and we want+    -- them to output at the first silent output setting, but then to stop.+    , qPutStr+    , qPutStrLn+    , qMessage+    , qMessageLn+    , qBracket+    ) where++import Control.Monad (when)+import Control.Monad.Trans (MonadIO, liftIO)+import System.Process (CreateProcess)+import System.Posix.EnvPlus (getEnv, modifyEnv)+import System.Process.Read.Chunks (Output, NonBlocking)+import System.Process.Read.Convenience (ePutStr, ePutStrLn)+import System.Process.Read.Monad (runProcessS, runProcessQ, runProcessD, runProcessV,+                                  runProcessSF, {-runProcessQF, runProcessDF,-} runProcessVF,+                                  runProcessSE, runProcessQE, runProcessDE)++quieter :: MonadIO m => Int -> m a -> m a+quieter n action = withModifiedVerbosity (\ v -> v - n) action+noisier :: MonadIO m => Int -> m a -> m a+noisier n action = withModifiedVerbosity (\ v -> v + n) action++withModifiedVerbosity :: MonadIO m => (Int -> Int) -> m a -> m a+withModifiedVerbosity f action =+    verbosity >>= \ v ->+    liftIO (modifyEnv "VERBOSITY" (const (Just (show (f v))))) >>+    -- ePutStr ("[" ++ show (f v) ++ "]") >>+    action >>= \ result ->+    -- ePutStr ("[" ++ show v ++ "]") >>+    liftIO (modifyEnv "VERBOSITY" (const (Just (show v)))) >>+    return result++defaultVerbosity :: Int+defaultVerbosity = 1++verbosity :: MonadIO m => m Int+verbosity = liftIO $ getEnv "VERBOSITY" >>= return . maybe 1 read++-- | Select from the runProcess* functions in Monad based on a verbosity level.+runProcess :: (NonBlocking s c, Enum c, MonadIO m) => CreateProcess -> s -> m [Output s]+runProcess p input = liftIO $+    verbosity >>= \ v ->+    case v of+      _ | v <= 0 -> runProcessS p input+      1 -> runProcessQ p input+      2 -> runProcessD p input+      _ -> runProcessV p input++-- | A version of 'runProcess' that throws an exception on failure.+runProcessF :: (NonBlocking s c, Enum c, MonadIO m) => Maybe (s, s) -> CreateProcess -> s -> m [Output s]+runProcessF prefixes p input = liftIO $+    verbosity >>= \ v ->+    case v of+      _ | v < 0 -> runProcessSF p input+      0 -> runProcessSE prefixes p input+      1 -> runProcessQE prefixes p input+      2 -> runProcessDE prefixes p input+      _ -> runProcessVF p input++qPutStrLn :: MonadIO m => String -> m ()+qPutStrLn s = verbosity >>= \ v -> when (v > 0) (ePutStrLn s)++qPutStr :: MonadIO m => String -> m ()+qPutStr s = verbosity >>= \ v -> when (v > 0) (ePutStr s)++qMessage :: MonadIO m => String -> a -> m a+qMessage s x = qPutStr s >> return x++qMessageLn :: MonadIO m => String -> a -> m a+qMessageLn s x = qPutStrLn s >> return x++qBracket :: MonadIO m => String -> m a -> m a+qBracket message action = do+  v <- verbosity+  case v of+    n | n < 1 -> action+    1 -> do+      qPutStr (message ++ "...")+      result <- quieter 1 action+      qPutStrLn "done."+      return result+    n -> do+      qPutStrLn message+      result <- quieter 1 action+      qPutStrLn (message ++ "...done.")+      return result
+ Tests/Main.hs view
@@ -0,0 +1,67 @@+module Main where++import qualified Codec.Binary.UTF8.String as UTF8+import qualified Data.ByteString.Lazy as L+import Data.ListLike (ListLike(concat))+import Prelude hiding (length, concat)+import System.Exit+import System.Posix.Files (getFileStatus, fileMode, setFileMode, unionFileModes, ownerExecuteMode, groupExecuteMode, otherExecuteMode)+import System.Process (proc, shell)+import System.Process.Progress (Output(..), readProcessChunks, keepStdout, discardStdout, collectOutputs)+import System.Process.Read.Chunks (readProcessChunks')+import Test.HUnit hiding (path)++main :: IO ()+main =+    do chmod "Tests/Test1.hs"+       chmod "Tests/Test2.hs"+       chmod "Tests/Test4.hs"+       (c,st) <- runTestText putTextToShowS test1 -- (TestList (versionTests ++ sourcesListTests ++ dependencyTests ++ changesTests))+       putStrLn (st "")+       case (failures c) + (errors c) of+         0 -> return ()+         _ -> exitFailure++encode :: String -> L.ByteString+encode = L.pack . UTF8.encode++chmod :: FilePath -> IO ()+chmod path =+    getFileStatus "Tests/Test1.hs" >>= \ status ->+    setFileMode path (foldr unionFileModes (fileMode status) [ownerExecuteMode, groupExecuteMode, otherExecuteMode])++test1 :: Test+test1 =+    TestLabel "test1"+      (TestList+       [ TestLabel "pnmfile3" $+         TestCase (do jpg <- L.readFile "Tests/penguin.jpg"+                      pnm <- readProcessChunks (proc "djpeg" []) jpg >>= return . concat . keepStdout :: IO L.ByteString+                      info <- readProcessChunks (proc "pnmfile" []) pnm >>= return . concat . keepStdout :: IO L.ByteString+                      assertEqual "pnmfile3" (encode "stdin:\tPPM raw, 96 by 96  maxval 255\n") info)+       , test2+       , TestLabel "readProcessChunks stdout stderr" $+         TestCase (do out <- readProcessChunks (shell "yes | head -10 | while read i; do echo stdout; echo stderr 1>&2; done") L.empty+                      let result = collectOutputs out+                      assertEqual "readProcessChunks stdout stderr" ([ExitSuccess], encode "stdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\n", encode "stderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\n", []) result)+       , test3+       , TestLabel "readProcessChunks' stdout stderr" $+         TestCase (do out <- readProcessChunks' (shell "yes | head -10 | while read i; do echo stdout; echo stderr 1>&2; done") L.empty+                      let result = collectOutputs out+                      assertEqual "readProcessChunks' stdout stderr" ([ExitSuccess], encode "stdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\nstdout\n", encode "stderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\nstderr\n", []) result)+{-+       , TestLabel "timed dot test" $+         TestCase (do output <- readModifiedProcess id (ShellCommand "Tests/Test2.hs") "" >>= return . take 10+                      assertEqual "timed dot test" ".........." output)+-}+       ])++test2 :: Test+test2 = TestLabel "readProcessChunks gzip" $+        TestCase (do result <- readProcessChunks (shell "gzip -v -f < Tests/penguin.jpg") L.empty+                     assertEqual "readProcessChunks gzip" [Stderr (encode "  2.0%\n"),Result ExitSuccess] (discardStdout result))++test3 :: Test+test3 = TestLabel "readProcessChunks' gzip'" $+        TestCase (do result <- readProcessChunks' (shell "gzip -v -f < Tests/penguin.jpg") L.empty+                     assertEqual "readProcessChunks' gzip" [Stderr (encode "  2.0%\n"),Result ExitSuccess] (discardStdout result))
+ changelog view
@@ -0,0 +1,64 @@+haskell-process-progress (0.9) unstable; urgency=low++  * Now $VERBOSITY<=0 means the qPutStr and qPutStrLn functions don't+    generate output, one or greater and they do.+  * Add qBracket++ -- David Fox <dsf@seereason.com>  Mon, 06 Jan 2014 13:25:29 -0800++haskell-process-progress (0.8) unstable; urgency=low++  * Move the NFData ExitCode instance here from debian-repo+  * Minor documentation updates++ -- David Fox <dsf@seereason.com>  Wed, 01 Jan 2014 09:40:50 -0800++haskell-process-progress (0.7) unstable; urgency=low++  * Fix the prefix feature, which prepends different prefixes to the+    lines of stdout and stderr.++ -- David Fox <dsf@seereason.com>  Mon, 23 Dec 2013 21:46:47 -0800++haskell-process-progress (0.6) unstable; urgency=low++  * Add option to echo output with prefixes on exception++ -- David Fox <dsf@seereason.com>  Mon, 09 Dec 2013 06:22:34 -0800++haskell-process-progress (0.5.3) unstable; urgency=low++  * Make changelog visible on hackage.++ -- David Fox <dsf@seereason.com>  Tue, 15 Oct 2013 07:39:38 -0700++haskell-process-progress (0.5.2) unstable; urgency=low++  * Fix the repository location in the cabal file.++ -- David Fox <dsf@seereason.com>  Sat, 31 Aug 2013 07:58:14 -0700++haskell-process-progress (0.5.1) unstable; urgency=low++  * Switch dependency from process-extras to process-listlike.++ -- David Fox <dsf@seereason.com>  Sat, 25 May 2013 08:22:42 -0700++haskell-process-progress (0.5) unstable; urgency=low++  * Changes for process-extras-0.6.++ -- David Fox <dsf@seereason.com>  Wed, 28 Nov 2012 09:59:27 -0800++haskell-process-progress (0.4.7) unstable; urgency=low++  * Bring over changes from retired darcs repository.++ -- David Fox <dsf@seereason.com>  Fri, 02 Nov 2012 16:47:06 -0700++haskell-process-progress (0.4.5) unstable; urgency=low++  * Debianization generated by cabal-debian++ -- SeeReason Autobuilder <partners@seereason.com>  Wed, 24 Oct 2012 15:18:01 -0700+
+ process-progress.cabal view
@@ -0,0 +1,48 @@+Name:               process-progress+Version:            0.9+Synopsis:           Run a process and do reportsing on its progress.+Description:        Function to run a process and wrappers to provide different+                    types of feedback while it executes.+Homepage:           https://src.seereason.com/process-progress+License:            BSD3+Author:             David Fox+Maintainer:         David Fox <dsf@seereason.com>+Category:           System+Build-type:         Simple+Cabal-version:      >=1.6+Extra-source-files: changelog++source-repository head+  Type:             darcs+  Location:         http://src.seereason.com/debian-tools/process-progress++Library+  ghc-options:      -Wall -O2 -threaded++  Exposed-modules:+    System.Posix.EnvPlus+    System.Process.Progress+    System.Process.Read.Chunks+    System.Process.Read.Compat+    System.Process.Read.Convenience+    System.Process.Read.Monad+    System.Process.Read.Verbosity++  Build-depends:+    base >= 4 && < 5,+    process,+    process-listlike >= 0.6,+    bytestring,+    deepseq,+    HUnit,+    ListLike,+    mtl,+    process,+    text,+    time,+    unix,+    utf8-string++Executable tests+  Main-Is: Tests/Main.hs+  GHC-Options: -Wall -O2 -threaded -rtsopts