process-listlike 0.9 → 0.10
raw patch · 30 files changed
+1191/−946 lines, 30 filesdep +regex-posixdep ~ListLikedep ~basedep ~bytestringsetup-changednew-component:exe:process-listlike-interactive-tests
Dependencies added: regex-posix
Dependency ranges changed: ListLike, base, bytestring, process
Files
- README.md +9/−0
- Setup.hs +1/−19
- System/Process/ByteString.hs +0/−24
- System/Process/ByteString/Lazy.hs +0/−24
- System/Process/Chunks.hs +0/−149
- System/Process/Lazy.hs +0/−34
- System/Process/ListLike.hs +0/−217
- System/Process/Strict.hs +0/−22
- System/Process/String.hs +0/−23
- System/Process/Text.hs +0/−24
- System/Process/Text/Lazy.hs +0/−24
- Tests/Main.hs +0/−215
- changelog +0/−135
- process-listlike.cabal +35/−36
- src/System/Process/ByteString.hs +28/−0
- src/System/Process/ByteString/Lazy.hs +28/−0
- src/System/Process/Chunks.hs +230/−0
- src/System/Process/ListLike.hs +8/−0
- src/System/Process/ListLike/Classes.hs +38/−0
- src/System/Process/ListLike/Instances.hs +43/−0
- src/System/Process/ListLike/LazyString.hs +15/−0
- src/System/Process/ListLike/Read.hs +165/−0
- src/System/Process/ListLike/ReadNoThreads.hs +173/−0
- src/System/Process/ListLike/StrictString.hs +9/−0
- src/System/Process/String.hs +19/−0
- src/System/Process/Text.hs +28/−0
- src/System/Process/Text/Lazy.hs +26/−0
- src/Utils.hs +10/−0
- tests/Interactive.hs +74/−0
- tests/Main.hs +252/−0
+ README.md view
@@ -0,0 +1,9 @@+# About++Extra functionality for the [Process library](http://hackage.haskell.org/package/process).++# Contributing++This project is available on [GitHub](https://github.com/davidlazar/process-extras) and [Bitbucket](https://bitbucket.org/davidlazar/process-extras/). You may contribute changes using either.++Please report bugs and feature requests using the [GitHub issue tracker](https://github.com/davidlazar/process-extras/issues).
Setup.hs view
@@ -1,20 +1,2 @@ import Distribution.Simple-import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(buildDir))-import Distribution.Simple.Program-import System.Directory-import System.Exit-import System.Process--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 ++ "/process-listlike-tests/process-listlike-tests") >>= \ code ->- if code == ExitSuccess then return () else error "unit test failure"+main = defaultMain
− System/Process/ByteString.hs
@@ -1,24 +0,0 @@--- | "System.Process.ListLike" functions restricted to type 'Data.ByteString.Char8.ByteString'.-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-module System.Process.ByteString- ( readProcess- , readProcessWithExitCode- , readCreateProcess- , readCreateProcessWithExitCode- ) where--import Data.ByteString.Char8 (ByteString)-import System.Exit (ExitCode)-import System.Process (CreateProcess)-import qualified System.Process.ListLike as R-import System.Process.Strict ()--readProcess :: (a ~ ByteString) => FilePath -> [String] -> a -> IO a-readProcess = R.readProcess-readProcessWithExitCode :: (a ~ ByteString) => FilePath -> [String] -> a -> IO (ExitCode, a, a)-readProcessWithExitCode = R.readProcessWithExitCode-readCreateProcess :: (a ~ ByteString) => CreateProcess -> a -> IO a-readCreateProcess = R.readCreateProcess-readCreateProcessWithExitCode :: (a ~ ByteString) => CreateProcess -> a -> IO (ExitCode, a, a)-readCreateProcessWithExitCode = R.readCreateProcessWithExitCode
− System/Process/ByteString/Lazy.hs
@@ -1,24 +0,0 @@--- | "System.Process.ListLike" functions restricted to the lazy 'Data.ByteString.Lazy.Char8.ByteString' type.-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-module System.Process.ByteString.Lazy- ( readProcess- , readProcessWithExitCode- , readCreateProcess- , readCreateProcessWithExitCode- ) where--import Data.ByteString.Lazy.Char8 (ByteString)-import System.Exit (ExitCode)-import System.Process (CreateProcess)-import qualified System.Process.ListLike as R-import System.Process.Lazy ()--readProcess ::(a ~ ByteString) => FilePath -> [String] -> a -> IO a-readProcess = R.readProcess-readProcessWithExitCode ::(a ~ ByteString) => FilePath -> [String] -> a -> IO (ExitCode, a, a)-readProcessWithExitCode = R.readProcessWithExitCode-readCreateProcess ::(a ~ ByteString) => CreateProcess -> a -> IO a-readCreateProcess = R.readCreateProcess-readCreateProcessWithExitCode ::(a ~ ByteString) => CreateProcess -> a -> IO (ExitCode, a, a)-readCreateProcessWithExitCode = R.readCreateProcessWithExitCode
− System/Process/Chunks.hs
@@ -1,149 +0,0 @@-{-# LANGUAGE ScopedTypeVariables, TypeFamilies #-}-module System.Process.Chunks- ( Chunk(..)- , readProcessChunks- -- * Control- , foldChunk- , foldChunks- , putChunk- -- * Canonical- , canonicalChunks- -- * Indent- , indentChunks- , putIndented- -- * Dotify- , dotifyChunk- , dotifyChunks- , putDots- ) where--import Control.Applicative ((<$>), (<*>))-import Control.DeepSeq (NFData)-import Control.Monad.State (StateT, evalStateT, evalState, get, put)-import Control.Monad.Trans (lift)-import Data.List (foldl')-import Data.ListLike (ListLike(..), ListLikeIO(..))-import Data.Monoid ((<>))-import Prelude hiding (mapM, putStr, null, tail, break, sequence, length, replicate, rem)-import System.Exit (ExitCode)-import System.IO (stderr)-import System.Process (ProcessHandle, CreateProcess)-import System.Process.ListLike (ListLikePlus, readProcessInterleaved)---- | This lets us use DeepSeq's 'Control.DeepSeq.force' on the stream--- of data returned by 'readProcessChunks'.-instance NFData ExitCode---- | The output stream of a process returned by 'readProcessChunks'.-data Chunk a- = ProcessHandle ProcessHandle -- ^ This will always come first- | Stdout a- | Stderr a- | Exception IOError- | Result ExitCode- deriving Show---- Is this rude? It will collide with any other bogus Show--- ProcessHandle instances created elsewhere.-instance Show ProcessHandle where- show _ = "<processhandle>"---- | A concrete use of 'readProcessInterleaved' - build a list--- containing chunks of process output, any exceptions that get thrown--- (unimplemented), and finally an exit code.-readProcessChunks :: (ListLikePlus a c) => CreateProcess -> a -> IO [Chunk a]-readProcessChunks p input =- readProcessInterleaved (\ h -> [ProcessHandle h]) (\ x -> [Result x]) (\ x -> [Stdout x]) (\ x -> [Stderr x]) p input--foldChunk :: (ProcessHandle -> b) -> (a -> b) -> (a -> b) -> (IOError -> b) -> (ExitCode -> b) -> Chunk a -> b-foldChunk pidf _ _ _ _ (ProcessHandle x) = pidf x-foldChunk _ outf _ _ _ (Stdout x) = outf x-foldChunk _ _ errf _ _ (Stderr x) = errf x-foldChunk _ _ _ exnf _ (Exception x) = exnf x-foldChunk _ _ _ _ exitf (Result x) = exitf x---- | Build a value from a chunk stream.-foldChunks :: (r -> Chunk a -> r) -> r -> [Chunk a] -> r-foldChunks f r0 xs = foldl' f r0 xs---- | Write the Stdout chunks to stdout and the Stderr chunks to stderr.-putChunk :: ListLikePlus a c => Chunk a -> IO ()-putChunk (Stdout x) = putStr x-putChunk (Stderr x) = hPutStr stderr x-putChunk _ = return ()---- | Merge adjacent Stdout or Stderr chunks.-canonicalChunks :: ListLikePlus a c => [Chunk a] -> [Chunk a]-canonicalChunks [] = []-canonicalChunks (Stdout a : Stdout b : more) = canonicalChunks (Stdout (a <> b) : more)-canonicalChunks (Stderr a : Stderr b : more) = canonicalChunks (Stderr (a <> b) : more)-canonicalChunks (Stdout a : more) | null a = canonicalChunks more-canonicalChunks (Stderr a : more) | null a = canonicalChunks more-canonicalChunks (a : more) = a : canonicalChunks more---- | The monad state, are we at the beginning of a line or the middle?-data BOL = BOL | MOL deriving (Eq)---- | Indent the text of a chunk with the prefixes given for stdout and--- stderr. The state monad keeps track of whether we are at the--- beginning of a line - when we are and more text comes we insert one--- of the prefixes.-indentChunk :: forall a c m. (Monad m, Functor m, ListLikePlus a c, Eq c) => c -> a -> a -> Chunk a -> StateT BOL m [Chunk a]-indentChunk nl outp errp chunk =- case chunk of- Stdout x -> doText Stdout outp x- Stderr x -> doText Stderr errp x- _ -> return [chunk]- where- doText :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]- doText con pre x = do- let (hd, tl) = break (== nl) x- (<>) <$> doHead con pre hd <*> doTail con pre tl- doHead :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]- doHead _ _ x | null x = return []- doHead con pre x = do- bol <- get- case bol of- BOL -> put MOL >> return [con (pre <> x)]- MOL -> return [con x]- doTail :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]- doTail _ _ x | null x = return []- doTail con pre x = do- bol <- get- put BOL- tl <- doText con pre (tail x)- return $ (if bol == BOL then [con pre] else []) <> [con (singleton nl)] <> tl---- | Pure function to indent the text of a chunk list.-indentChunks :: (ListLikePlus a c, Eq c) => c -> a -> a -> [Chunk a] -> [Chunk a]-indentChunks nl outp errp chunks =- evalState (Prelude.concat <$> mapM (indentChunk nl outp errp) chunks) BOL---- | Output the indented text of a chunk list, but return the original--- unindented list.-putIndented :: (ListLikePlus a c, Eq c) => c -> a -> a -> [Chunk a] -> IO [Chunk a]-putIndented nl outp errp chunks =- evalStateT (mapM (\ x -> indentChunk nl outp errp x >>= mapM_ (lift . putChunk) >> return x) chunks) BOL--dotifyChunk :: forall a c m. (Monad m, Functor m, ListLikePlus a c) => Int -> c -> Chunk a -> StateT Int m [Chunk a]-dotifyChunk charsPerDot dot chunk =- case chunk of- Stdout x -> doChars (length x)- Stderr x -> doChars (length x)- _ -> return [chunk]- where- doChars count = do- rem <- get- let (count', rem') = divMod (rem + count) (fromIntegral charsPerDot)- put rem'- if (count' > 0) then return [Stderr (replicate count' dot)] else return []--dotifyChunks :: forall a c. (ListLikePlus a c) => Int -> c -> [Chunk a] -> [Chunk a]-dotifyChunks charsPerDot dot chunks =- evalState (Prelude.concat <$> mapM (dotifyChunk charsPerDot dot) chunks) 0---- | Output the dotified text of a chunk list, but return the original--- unindented list.-putDots :: (ListLikePlus a c) => Int -> c -> [Chunk a] -> IO [Chunk a]-putDots charsPerDot dot chunks =- evalStateT (mapM (\ x -> dotifyChunk charsPerDot dot x >>= mapM_ (lift . putChunk) >> return x) chunks) 0
− System/Process/Lazy.hs
@@ -1,34 +0,0 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module System.Process.Lazy where--import Control.Exception as E (evaluate)-import Control.Monad-import qualified Data.ByteString.Lazy as L-import Data.List as List (map, concat)-import Data.ListLike (ListLikeIO(..))-import Data.ListLike.Text.Text ()-import Data.ListLike.Text.TextLazy ()-import qualified Data.Text as T-import qualified Data.Text.Lazy as LT-import Data.Word (Word8)-import Prelude hiding (null, length, rem)-import System.IO hiding (hPutStr, hGetContents)-import System.Process.ListLike (ListLikePlus(..))--instance ListLikePlus L.ByteString Word8 where- setModes _ (inh, outh, errh, _) = f inh >> f outh >> f errh where f mh = maybe (return ()) (\ h -> hSetBinaryMode h True) mh- readChunks h = hGetContents h >>= evaluate . Prelude.map (L.fromChunks . (: [])) . L.toChunks--instance ListLikePlus LT.Text Char where- setModes _ _ = return ()- readChunks h = hGetContents h >>= evaluate . Prelude.map (LT.fromChunks . (: [])) . LT.toChunks---- | This String instance is implemented using the Lazy Text instance.--- Otherwise (without some serious coding) String would be a strict--- instance . Note that the 'System.Process.readProcess' in the--- process library is strict, while our equivalent is not - see test4--- in Tests/Dots.hs.-instance ListLikePlus String Char where- setModes _ _ = return ()- readChunks h = readChunks h >>= return . List.map T.unpack . List.concat . List.map LT.toChunks
− System/Process/ListLike.hs
@@ -1,217 +0,0 @@--- | Generalized versions of the functions--- 'System.Process.readProcess', and--- 'System.Process.readProcessWithExitCode'.-{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies #-}-module System.Process.ListLike (- ListLikePlus(..),- readProcessInterleaved,- readInterleaved,- readCreateProcessWithExitCode,- readCreateProcess,- readProcessWithExitCode,- readProcess,- showCmdSpecForUser- ) where--import Control.Concurrent-import Control.Exception as E (SomeException, onException, catch, try, throwIO, mask, throw)-import Control.Monad-import Data.ListLike (ListLike(..), ListLikeIO(..))-import Data.ListLike.Text.Text ()-import Data.ListLike.Text.TextLazy ()-import Data.Monoid (Monoid(mempty, mappend), (<>))-import GHC.IO.Exception (IOErrorType(OtherError, ResourceVanished), IOException(ioe_type))-import Prelude hiding (null, length, rem)-import System.Exit (ExitCode(ExitSuccess, ExitFailure))-import System.IO hiding (hPutStr, hGetContents)-import qualified System.IO.Error as IO-import System.IO.Unsafe (unsafeInterleaveIO)-import System.Process (ProcessHandle, CreateProcess(..), StdStream(CreatePipe, Inherit), proc,- CmdSpec(RawCommand, ShellCommand), showCommandForUser,- createProcess, waitForProcess, terminateProcess)---- | Class of types which can be used as the input and outputs of--- these process functions.-class ListLikeIO a c => ListLikePlus a c where- setModes :: a -> (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO ()- -- ^ Perform initialization on the handles returned by createProcess- -- based on this ListLikePlus instance - typically setting binary- -- mode on all the file descriptors if the element type is Word8.- -- If this is not done, reading something other than text (such as a- -- .jpg or .pdf file) will usually fail with a decoding error.- readChunks :: Handle -> IO [a]- -- ^ Read the list of chunks from this handle. For lazy types this- -- is just a call to hGetContents followed by toChunks. For strict- -- types it might return a singleton list. Strings are trickier.---- | Read the output of a process and use the argument functions to--- convert it into a Monoid, preserving the order of appearance of the--- different chunks of output from standard output and standard error.-readProcessInterleaved :: (ListLikePlus a c, Monoid b) =>- (ProcessHandle -> b) -> (ExitCode -> b) -> (a -> b) -> (a -> b)- -> CreateProcess -> a -> IO b-readProcessInterleaved pidf codef outf errf p input = mask $ \ restore -> do- hs@(Just inh, Just outh, Just errh, pid) <-- createProcess (p {std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe })-- setModes input hs-- flip onException- (do hClose inh; hClose outh; hClose errh;- terminateProcess pid; waitForProcess pid) $ restore $ do- waitOut <- forkWait $ readInterleaved (pidf pid) [(outf, outh), (errf, errh)] $ waitForProcess pid >>= return . codef- writeInput inh input- waitOut---- | Simultaneously read the output from several file handles, using--- the associated functions to add them to a Monoid b in the order--- they appear. This closes each handle on EOF, because AFAIK it is--- the only useful thing to do with a file handle that has reached--- EOF.-readInterleaved :: forall a b c. (ListLikePlus a c, Monoid b) => b -> [(a -> b, Handle)] -> IO b -> IO b-readInterleaved start pairs finish = newEmptyMVar >>= readInterleaved' start pairs finish--readInterleaved' :: forall a b c. (ListLikePlus a c, Monoid b) =>- b -> [(a -> b, Handle)] -> IO b -> MVar (Either Handle b) -> IO b-readInterleaved' start pairs finish res = do- mapM_ (forkIO . uncurry readHandle) pairs- r <- takeChunks (length pairs)- return $ start <> r- where- readHandle :: (a -> b) -> Handle -> IO ()- readHandle f h = do- cs <- readChunks h- -- If the type returned as stdout and stderr is lazy we need- -- to force it here in the producer thread - I'm not exactly- -- sure why. And why is String lazy?- -- when (lazy (undefined :: a)) (void cs)- mapM_ (\ c -> putMVar res (Right (f c))) cs- hClose h- putMVar res (Left h)- takeChunks :: Int -> IO b- takeChunks 0 = finish- takeChunks openCount = takeMVar res >>= takeChunk openCount- takeChunk :: Int -> Either Handle b -> IO b- takeChunk openCount (Left h) = hClose h >> takeChunks (openCount - 1)- takeChunk openCount (Right x) =- do xs <- unsafeInterleaveIO $ takeChunks openCount- return (x <> xs)---- | An implementation of 'System.Process.readProcessWithExitCode'--- with a two generalizations: (1) The input and outputs can be any--- instance of 'ListLikePlus', and (2) The CreateProcess is passes an--- argument, so you can use either 'System.Process.proc' or--- 'System.Process.rawSystem' and you can modify its fields such as--- 'System.Process.cwd' before the process starts-readCreateProcessWithExitCode- :: forall a c.- ListLikePlus a c =>- CreateProcess -- ^ process to run- -> a -- ^ standard input- -> IO (ExitCode, a, a) -- ^ exitcode, stdout, stderr, exception-readCreateProcessWithExitCode p input =- readProcessInterleaved (\ _ -> mempty)- (\ c -> (c, mempty, mempty))- (\ x -> (mempty, x, mempty))- (\ x -> (mempty, mempty, x))- p input---- | A version of 'System.Process.readProcessWithExitCode' that uses--- any instance of 'ListLikePlus' instead of 'String', implemented--- using 'readCreateProcessWithExitCode'.-readProcessWithExitCode- :: ListLikePlus a c =>- FilePath -- ^ command to run- -> [String] -- ^ any arguments- -> a -- ^ standard input- -> IO (ExitCode, a, a) -- ^ exitcode, stdout, stderr-readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input---- | Implementation of 'System.Process.readProcess' that uses any--- instance of 'ListLikePlus' instead of 'String', implemented using--- 'readCreateProcess'.-readProcess- :: ListLikePlus a c =>- FilePath -- ^ command to run- -> [String] -- ^ any arguments- -> a -- ^ standard input- -> IO a -- ^ stdout-readProcess cmd args = readCreateProcess (proc cmd args)---- | An implementation of 'System.Process.readProcess' with a two--- generalizations: (1) The input and outputs can be any instance of--- 'ListLikePlus', and (2) The CreateProcess is passes an argument, so--- you can use either 'System.Process.proc' or--- 'System.Process.rawSystem' and you can modify its fields such as--- 'System.Process.cwd' before the process starts------ This can't be implemented by calling readProcessInterleaved because--- the std_err field needs to be set to Inherit, which means that--- 'createProcess' returns no stderr handle. Instead, we have a--- nearly identical copy of the 'readProcessInterleaved' code which--- only passes one pair 'readInterleaved'. REMEMBER to keep these two--- in sync if there are future changes!-readCreateProcess- :: ListLikePlus a c =>- CreateProcess -- ^ process to run- -> a -- ^ standard input- -> IO a -- ^ stdout-readCreateProcess p input = mask $ \restore -> do- hs@(Just inh, Just outh, _, pid) <-- createProcess (p {std_in = CreatePipe, std_out = CreatePipe, std_err = Inherit })-- setModes input hs-- flip onException- (do hClose inh; hClose outh;- terminateProcess pid; waitForProcess pid) $ restore $ do- waitOut <- forkWait $ readInterleaved mempty [(id, outh)] $ waitForProcess pid >>= codef- writeInput inh input- waitOut-- where- -- Throw an IO error on ExitFailure- codef (ExitFailure r) = throw (mkError "readCreateProcess: " (cmdspec p) r)- codef ExitSuccess = return mempty---- | Write and flush process input, closing the handle when done.--- Catch and ignore Resource Vanished exceptions, they just mean the--- process exited before all of its output was read.-writeInput :: ListLikePlus a c => Handle -> a -> IO ()-writeInput inh input = do- (do unless (null input) (hPutStr inh input >> hFlush inh)- hClose inh) `E.catch` resourceVanished (\ _ -> 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---- | Fork a thread to read from a handle.-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)---- | Create an exception for a process that exited abnormally.-mkError :: String -> CmdSpec -> Int -> IOError-mkError prefix (RawCommand cmd args) r =- IO.mkIOError OtherError (prefix ++ showCommandForUser cmd args ++ " (exit " ++ show r ++ ")")- Nothing Nothing-mkError prefix (ShellCommand cmd) r =- IO.mkIOError OtherError (prefix ++ cmd ++ " (exit " ++ show r ++ ")")- Nothing Nothing---- | A chunk stream should have an 'ExitCode' at the end, this monoid lets--- us build a monoid for the type returned by readProcessWithExitCode.-instance Monoid ExitCode where- mempty = ExitFailure 0- mappend x (ExitFailure 0) = x- mappend _ x = x--showCmdSpecForUser :: CmdSpec -> String-showCmdSpecForUser (ShellCommand s) = s-showCmdSpecForUser (RawCommand p args) = showCommandForUser p args
− System/Process/Strict.hs
@@ -1,22 +0,0 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies, TypeSynonymInstances #-}--- | ListLikePlus instances for strict types - these are more--- dangerous, if you start a long running process with them they will--- block until the process finishes. Why not try a lazy type?-{-# OPTIONS_GHC -fno-warn-orphans #-}-module System.Process.Strict where--import Control.DeepSeq (force)-import Data.ByteString.Char8 as B (ByteString)-import Data.ListLike.IO (hGetContents)-import Data.Text as T (Text)-import Data.Word (Word8)-import System.IO (hSetBinaryMode)-import System.Process.ListLike (ListLikePlus(..))--instance ListLikePlus B.ByteString Word8 where- setModes _ (inh, outh, errh, _) = f inh >> f outh >> f errh where f mh = maybe (return ()) (\ h -> hSetBinaryMode h True) mh- readChunks h = hGetContents h >>= return . force . (: [])--instance ListLikePlus T.Text Char where- setModes _ _ = return ()- readChunks h = hGetContents h >>= return . force . (: [])
− System/Process/String.hs
@@ -1,23 +0,0 @@--- | "System.Process.ListLike" functions restricted to type 'String'.-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-module System.Process.String- ( readProcess- , readProcessWithExitCode- , readCreateProcess- , readCreateProcessWithExitCode- ) where--import System.Exit (ExitCode)-import System.Process (CreateProcess)-import qualified System.Process.ListLike as R-import System.Process.Lazy ()--readProcess :: (a ~ String) => FilePath -> [String] -> a -> IO a-readProcess = R.readProcess-readProcessWithExitCode :: (a ~ String) => FilePath -> [String] -> a -> IO (ExitCode, a, a)-readProcessWithExitCode = R.readProcessWithExitCode-readCreateProcess :: (a ~ String) => CreateProcess -> a -> IO a-readCreateProcess = R.readCreateProcess-readCreateProcessWithExitCode :: (a ~ String) => CreateProcess -> a -> IO (ExitCode, a, a)-readCreateProcessWithExitCode = R.readCreateProcessWithExitCode
− System/Process/Text.hs
@@ -1,24 +0,0 @@--- | "System.Process.ListLike" functions restricted to type 'Data.Text.Text'.-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-module System.Process.Text- ( readProcess- , readProcessWithExitCode- , readCreateProcess- , readCreateProcessWithExitCode- ) where--import Data.Text (Text)-import System.Exit (ExitCode)-import System.Process (CreateProcess)-import qualified System.Process.ListLike as R-import System.Process.Strict ()--readProcess :: (a ~ Text) => FilePath -> [String] -> a -> IO a-readProcess = R.readProcess-readProcessWithExitCode :: (a ~ Text) => FilePath -> [String] -> a -> IO (ExitCode, a, a)-readProcessWithExitCode = R.readProcessWithExitCode-readCreateProcess :: (a ~ Text) => CreateProcess -> a -> IO a-readCreateProcess = R.readCreateProcess-readCreateProcessWithExitCode :: (a ~ Text) => CreateProcess -> a -> IO (ExitCode, a, a)-readCreateProcessWithExitCode = R.readCreateProcessWithExitCode
− System/Process/Text/Lazy.hs
@@ -1,24 +0,0 @@--- | "System.Process.ListLike" functions restricted to the lazy 'Data.Text.Lazy.Text' type.-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-module System.Process.Text.Lazy- ( readProcess- , readProcessWithExitCode- , readCreateProcess- , readCreateProcessWithExitCode- ) where--import Data.Text.Lazy (Text)-import System.Exit (ExitCode)-import System.Process (CreateProcess)-import qualified System.Process.ListLike as R-import System.Process.Lazy ()--readProcess :: (a ~ Text) => FilePath -> [String] -> a -> IO a-readProcess = R.readProcess-readProcessWithExitCode :: (a ~ Text) => FilePath -> [String] -> a -> IO (ExitCode, a, a)-readProcessWithExitCode = R.readProcessWithExitCode-readCreateProcess :: (a ~ Text) => CreateProcess -> a -> IO a-readCreateProcess = R.readCreateProcess-readCreateProcessWithExitCode :: (a ~ Text) => CreateProcess -> a -> IO (ExitCode, a, a)-readCreateProcessWithExitCode = R.readCreateProcessWithExitCode
− Tests/Main.hs
@@ -1,215 +0,0 @@-{-# LANGUAGE FlexibleInstances, OverloadedStrings, RankNTypes #-}-module Main where--import Codec.Binary.UTF8.String (encode)-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as L-import Data.ListLike as ListLike (ListLike(..))-import Data.Maybe (mapMaybe)-import Data.Monoid (Monoid(..))-import qualified Data.Text as T-import qualified Data.Text.Lazy as LT-import Prelude hiding (length, concat)-import GHC.IO.Exception-import System.Exit-import System.Posix.Files (getFileStatus, fileMode, setFileMode, unionFileModes, ownerExecuteMode, groupExecuteMode, otherExecuteMode)-import System.Process (proc)-import System.Process.Chunks (Chunk(..), readProcessChunks, canonicalChunks)-import System.Process.Lazy ()-import System.Process.ListLike (readProcessWithExitCode, readCreateProcessWithExitCode, readCreateProcess, ListLikePlus(..))-import System.Process.Strict ()-import Test.HUnit hiding (path)--fromString :: String -> B.ByteString-fromString = fromList . encode--lazyFromString :: String -> L.ByteString-lazyFromString = L.fromChunks . (: []) . fromString--instance Monoid Test where- mempty = TestList []- mappend (TestList a) (TestList b) = TestList (a ++ b)- mappend (TestList a) b = TestList (a ++ [b])- mappend a (TestList b) = TestList ([a] ++ b)- mappend a b = TestList [a, b]--testInstances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testInstances mkTest = mappend (testCharInstances mkTest) (testWord8Instances mkTest)--testStrictInstances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testStrictInstances mkTest = mappend (testStrictCharInstances mkTest) (testStrictWord8Instances mkTest)--testLazyInstances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testLazyInstances mkTest = mappend (testLazyCharInstances mkTest) (testLazyWord8Instances mkTest)--testCharInstances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testCharInstances mkTest = mappend (testLazyCharInstances mkTest) (testStrictCharInstances mkTest)--testLazyCharInstances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testLazyCharInstances mkTest =- TestList [ TestLabel "Lazy Text" $ mkTest LT.empty- , TestLabel "String" $ mkTest ("" :: String) ]--testStrictCharInstances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testStrictCharInstances mkTest =- TestList [ TestLabel "Strict Text" $ mkTest T.empty ]--testWord8Instances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testWord8Instances mkTest = mappend (testLazyWord8Instances mkTest) (testStrictWord8Instances mkTest)--testLazyWord8Instances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testLazyWord8Instances mkTest =- TestList [ TestLabel "Lazy ByteString" $ mkTest L.empty ]--testStrictWord8Instances :: (forall a c. (Show a, ListLikePlus a c) => a -> Test) -> Test-testStrictWord8Instances mkTest =- TestList [ TestLabel "Strict ByteString" $ mkTest B.empty ]--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--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 "[Output]" $- TestList- [ testCharInstances- (\ i -> TestCase (do b <- readProcessChunks (proc "cat" ["Tests/text"]) i- assertEqual- "UTF8"- ["ProcessHandle <processhandle>",- -- For Text, assuming your locale is set to utf8, the result is unicode.- "Stdout \"Signed: Baishi laoren \\30333\\30707\\32769\\20154, painted in the artist\\8217s 87th year.\\n\"",- "Result ExitSuccess"] (ListLike.map show (canonicalChunks b))))- , testWord8Instances- (\ i -> TestCase (do b <- readProcessChunks (proc "cat" ["Tests/text"]) i- assertEqual- "UTF8"- ["ProcessHandle <processhandle>",- -- For ByteString we get utf8 encoded text- "Stdout \"Signed: Baishi laoren \\231\\153\\189\\231\\159\\179\\232\\128\\129\\228\\186\\186, painted in the artist\\226\\128\\153s 87th year.\\n\"",- "Result ExitSuccess"] (ListLike.map show (canonicalChunks b))))- , testInstances- (\ i -> TestCase (do b <- readProcessChunks (proc "Tests/Test1.hs" []) i- assertEqual- "[Chunk]"- ["ProcessHandle <processhandle>",- "Stderr \"This is an error message.\\n\"",- "Result (ExitFailure 123)"]- (ListLike.map show (canonicalChunks b))))- ]- -- This gets "hGetContents: invalid argument (invalid byte sequence)" if we don't call- -- binary on the file handles in readProcessInterleaved.-- , TestLabel "JPG" $- TestList- [ {- TestCase (do b <- readProcessChunks (proc "cat" ["Tests/houseisclean.jpg"]) B.empty >>=- return . mapMaybe (\ x -> case x of- Stdout s -> Just (length' s)- Stderr s -> Just (length' s)- _ -> Nothing)- assertEqual "ByteString Chunk Size"- [68668,0]- b- -- If we could read a jpg file into a string the chunks would look something like this:- -- [2048,2048,2048,1952,2048,2048,2048,1952,2048,2048,2048,1952,2048,2048,2048,1952,2048,2048,2048,1952,2048,2048,2048,1952,2048,2048,2048,1952,2048,2048,2048,1952,2048,1852]- )- , -}- testLazyWord8Instances- ( \ i -> TestCase (do b <- readProcessChunks (proc "cat" ["Tests/houseisclean.jpg"]) i >>=- return . mapMaybe (\ x -> case x of- Stdout s -> Just (length s)- Stderr s -> Just (length s)- _ -> Nothing)- assertEqual "Chunk Size" [32752,32752,3164] b))- , testStrictWord8Instances- ( \ i -> TestCase (do b <- readProcessChunks (proc "cat" ["Tests/houseisclean.jpg"]) i >>=- return . mapMaybe (\ x -> case x of- Stdout s -> Just (length s)- Stderr s -> Just (length s)- _ -> Nothing)- assertEqual "Chunk Size" [68668,0] b))-{- -- We don't seem to get an InvalidArgument exception back.- , TestCase (do b <- tryIOError (readProcessChunks (proc "cat" ["Tests/houseisclean.jpg"]) "") >>= return . either Left (Right . show)- assertEqual "String decoding exception" (Left (IOError { ioe_handle = Nothing- , ioe_type = InvalidArgument- , ioe_location = "recoverDecode"- , ioe_description = "invalid byte sequence"- , ioe_errno = Nothing- , ioe_filename = Nothing })) b)--Related to https://ghc.haskell.org/trac/ghc/ticket/9236. Try this:-- import System.IO- import System.IO.Error-- main = do- h <- openFile "Tests/houseisclean.jpg" ReadMode- r <- try (hGetContents h) >>= either exn str- hClose h- where- exn (e :: IOError) = putStrLn ("exn=" ++ show (ioe_handle e, ioe_type e, ioe_location e, ioe_description e, ioe_errno e, ioe_filename e))- str s = putStrLn ("s=" ++ show s)--The exception gets thrown and caught after the string result starts-being printed. You can see the open quote.--}- ]-{-- , TestLabel "ByteString" $- TestCase (do b <- readProcessWithExitCode "Tests/Test1.hs" [] B.empty- assertEqual "ByteString" (ExitFailure 123, fromString "", fromString "This is an error message.\n") b)--}- , TestLabel "Lazy" $- TestCase (do l <- readProcessWithExitCode "Tests/Test1.hs" [] L.empty- assertEqual "Lazy ByteString" (ExitFailure 123, lazyFromString "", lazyFromString "This is an error message.\n") l)-{-- , TestLabel "Text" $- TestCase (do t <- readProcessWithExitCode "Tests/Test1.hs" [] T.empty- assertEqual "Text" (ExitFailure 123, T.pack "", T.pack "This is an error message.\n") t)--}- , TestLabel "LazyText" $- TestCase (do lt <- readProcessWithExitCode "Tests/Test1.hs" [] LT.empty- assertEqual "LazyText" (ExitFailure 123, LT.pack "", LT.pack "This is an error message.\n") lt)-{-- , TestLabel "String" $- TestCase (do s <- readProcessWithExitCode "Tests/Test1.hs" [] ""- assertEqual "String" (ExitFailure 123, "", "This is an error message.\n") s)--}- , TestLabel "pnmfile" $- TestCase (do out <- L.readFile "Tests/penguin.jpg" >>=- readCreateProcess (proc "djpeg" []) >>=- readCreateProcess (proc "pnmfile" [])- assertEqual "pnmfile" (lazyFromString "stdin:\tPPM raw, 96 by 96 maxval 255\n") out)- , TestLabel "pnmfile2" $- TestCase (do jpg <- L.readFile "Tests/penguin.jpg"- (code1, pnm, err1) <- readCreateProcessWithExitCode (proc "djpeg" []) jpg- out2 <- readCreateProcess (proc "pnmfile" []) pnm- assertEqual "pnmfile2" (ExitSuccess, empty, 2192, 27661, lazyFromString "stdin:\tPPM raw, 96 by 96 maxval 255\n") (code1, err1, length jpg, length pnm, out2))- , TestLabel "file closed 1" $- TestCase (do result <- readCreateProcessWithExitCode (proc "Tests/Test4.hs" []) (lazyFromString "a" :: L.ByteString)- assertEqual "file closed 1" (ExitSuccess, (lazyFromString "a"), (lazyFromString "Read one character: 'a'\n")) result)- , TestLabel "file closed 2" $- TestCase (do result <- readCreateProcessWithExitCode (proc "Tests/Test4.hs" []) (lazyFromString "" :: L.ByteString)- assertEqual "file closed 2" (ExitFailure 1, empty, (lazyFromString "Test4.hs: <stdin>: hGetChar: end of file\n")) result)- , TestLabel "file closed 3" $- TestCase (do result <- readCreateProcessWithExitCode (proc "Tests/Test4.hs" []) ("abcde" :: LT.Text)- assertEqual "file closed 3" (ExitSuccess, "a", "Read one character: 'a'\n") result)- , TestLabel "file closed 4" $- TestCase (do result <- readCreateProcessWithExitCode (proc "Tests/Test4.hs" []) ("abcde" :: LT.Text)- assertEqual "file closed 4" (ExitSuccess, "a", "Read one character: 'a'\n") result)- ])
− changelog
@@ -1,135 +0,0 @@-haskell-process-listlike (0.9) unstable; urgency=low-- * Add an argument to readProcessInterleaved that receives the- ProcessHandle as the process starts. Add a corresponding constructor- to the Output type to insert the ProcessHandle into the list- returned by readProcessChunks.-- -- David Fox <dsf@seereason.com> Sun, 31 Aug 2014 07:15:28 -0700--haskell-process-listlike (0.8) unstable; urgency=low-- * Replace the implementation of readCreateProcessWithExitCode- with a call to readProcessInterleaved. Almost did the same- with readCreateProcess, but it was simpler to make it a near- copy of readProcessInterleaved.-- -- David Fox <dsf@seereason.com> Sat, 30 Aug 2014 13:08:47 -0700--haskell-process-listlike (0.7) unstable; urgency=low-- * Add module System.Process.Read.Interleaved (from process-progress)- * Add module System.Process.Read.Chunks (from process-progress)- * Rename module System.Process.Read.Chars -> System.Process.Read.ListLike-- -- David Fox <dsf@seereason.com> Tue, 26 Aug 2014 11:16:13 -0700--haskell-process-listlike (0.6.2) unstable; urgency=low-- * Make changelog visible in hackage.-- -- David Fox <dsf@seereason.com> Tue, 15 Oct 2013 07:40:46 -0700--haskell-process-listlike (0.6.1) unstable; urgency=low-- * Add System.Process.String.-- -- David Fox <dsf@seereason.com> Wed, 22 May 2013 11:22:06 -0700--haskell-process-listlike (0.6) unstable; urgency=low-- * Use the ListLike and ListLikeIO classes as the basis of the- class of types the readProcess functions use.- * Move the Chunk code to the process-progress package.-- -- David Fox <dsf@seereason.com> Wed, 28 Nov 2012 08:17:47 -0800--haskell-process-extras (0.5) unstable; urgency=low-- * Modify the signature of the readModifiedProcess* functions- to take a CreateProcess rather than a function to modify a- CreateProcess.-- -- David Fox <dsf@seereason.com> Mon, 12 Nov 2012 18:37:40 -0800--haskell-process-extras (0.4.7) unstable; urgency=low-- * Add compatibility modules for the original process-extras.-- -- David Fox <dsf@seereason.com> Wed, 24 Oct 2012 14:46:59 -0700--haskell-process-extras (0.4.6) unstable; urgency=low-- * Forked off process-progress.-- -- David Fox <dsf@seereason.com> Wed, 24 Oct 2012 11:31:34 -0700--haskell-process-extras (0.4.5) unstable; urgency=low-- * Add Verbosity module- * Print newline after last dot-- -- David Fox <dsf@seereason.com> Tue, 23 Oct 2012 10:33:25 -0700--haskell-process-extras (0.4.4) unstable; urgency=low-- * Replace all the lazyCommand*/lazyProcess* functions with runProcess*.-- -- David Fox <dsf@seereason.com> Tue, 23 Oct 2012 08:07:37 -0700--haskell-process-extras (0.4.3) unstable; urgency=low-- * More cleanups in readProcessChunks- * Unit tests- * Start of thread based version of readProcessChunks- * Start of pipes based version of readProcessChunks-- -- David Fox <dsf@seereason.com> Mon, 22 Oct 2012 16:15:14 -0700--haskell-process-extras (0.4.1) unstable; urgency=low-- * Redo and simplify readProcessChunks so it works on infinite streams without- hanging or leaking memory.- * Add some tests (though not in a formal framework yet.)-- -- David Fox <dsf@seereason.com> Sat, 20 Oct 2012 15:24:15 -0700--haskell-process-extras (0.4) unstable; urgency=low-- * Re-arrange the modules so that we can generally just import- System.Process.Read.- * Rename readProcessChunksWithExitCode -> readProcessChunks- * Rename class Strng -> class Chars- * Rename class Strng2 -> class NonBlocking-- -- David Fox <dsf@seereason.com> Thu, 18 Oct 2012 08:13:36 -0700--haskell-process-extras (0.3.8) unstable; urgency=low-- * No, we still need to catch ResourceVanished. Ingoring it seems to- be sufficient, don't change the signatures back to indicate when it- happens.-- -- David Fox <dsf@seereason.com> Wed, 17 Oct 2012 23:52:21 -0700--haskell-process-extras (0.3.7) unstable; urgency=low-- * The code that catches ResourceVanished seems to be unnecessary now,- remove it.-- -- David Fox <dsf@seereason.com> Wed, 17 Oct 2012 15:30:52 -0700--haskell-process-extras (0.3.6) unstable; urgency=low-- * Call hSetBinaryMode in all the ByteString instances of Strng- * Fix a bug which prevented stderr from being forced- * Change to emulate behavior of System.Process for input handle-- -- David Fox <dsf@seereason.com> Wed, 17 Oct 2012 15:02:09 -0700--haskell-process-extras (0.3.5) unstable; urgency=low-- * Add instances and implementations for the String type.-- -- David Fox <dsf@seereason.com> Wed, 17 Oct 2012 09:41:03 -0700-
process-listlike.cabal view
@@ -1,61 +1,60 @@ Name: process-listlike-Version: 0.9-Synopsis: Enhanced version of process-extras-Description: Extra functionality for the Process library <http://hackage.haskell.org/package/process>.- This is a drop-in replacement for <http://hackage.haskell.org/package/process-extras>,- which adds support for creating processes from a CreateProcess, more access to the- internals, and completes support for the String type.-Homepage: http://src.seereason.com/process-listlike+Version: 0.10+Synopsis: Process extras+Description: Extra functionality for the Process library+ <http://hackage.haskell.org/package/process>.+Homepage: https://github.com/ddssff/process-listlike License: MIT License-file: LICENSE-Author: David Lazar, Bas van Dijk, David Fox-Maintainer: David Fox <dsf@seereason.com>+Author: David Lazar, Bas van Dijk+Maintainer: David Lazar <lazar6@illinois.edu> Category: System Build-type: Simple-Cabal-version: >=1.8-Extra-Source-Files: changelog+Cabal-version: >= 1.8+Extra-source-files:+ README.md source-repository head- Type: darcs- Location: https://github.com/ddssff/process-listlike.git+ Type: git+ Location: https://github.com/ddssff/process-listlike Library- ghc-options: -Wall -O2+ ghc-options: -Wall + Hs-source-dirs: src+ Exposed-modules:- System.Process.Chunks- System.Process.Lazy- System.Process.ListLike System.Process.ByteString System.Process.ByteString.Lazy- System.Process.Strict+ System.Process.Chunks+ System.Process.ListLike+ System.Process.ListLike.Classes+ System.Process.ListLike.Instances+ System.Process.ListLike.LazyString+ System.Process.ListLike.Read+ System.Process.ListLike.ReadNoThreads+ System.Process.ListLike.StrictString System.Process.String System.Process.Text System.Process.Text.Lazy + Other-modules:+ Utils+ Build-depends: base >= 4 && < 5,+ process, bytestring,- deepseq,- HUnit,- ListLike >= 4.0,+ ListLike >= 4, mtl,- process, text,- utf8-string+ deepseq Executable process-listlike-tests- Main-Is: Tests/Main.hs+ Main-Is: tests/Main.hs+ Build-depends: base >= 4, bytestring, deepseq, process, process-listlike, utf8-string, unix, text, HUnit, ListLike, regex-posix++Executable process-listlike-interactive-tests+ Main-Is: tests/Interactive.hs GHC-Options: -Wall -O2 -threaded -rtsopts- Build-Depends:- base >= 4 && < 5,- bytestring,- deepseq,- HUnit,- ListLike,- mtl,- process,- process-listlike,- text,- utf8-string,- unix+ Build-Depends: base >= 4, bytestring, deepseq, HUnit, ListLike, mtl, process, process-listlike, text, utf8-string, unix
+ src/System/Process/ByteString.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE FlexibleContexts #-}+module System.Process.ByteString where++import Data.ByteString (ByteString)+import System.Process+import System.Process.ListLike.Classes (ProcessOutput)+import qualified System.Process.ListLike.Read as LL (readCreateProcess, readProcess)+import System.Exit (ExitCode)++readCreateProcess :: ProcessOutput ByteString b => CreateProcess -> ByteString -> IO b+readCreateProcess = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but takes a+-- CreateProcess instead of a command and argument list, and reads and+-- writes type 'ByteString'+readCreateProcessWithExitCode+ :: CreateProcess -- ^ command to run+ -> ByteString -- ^ standard input+ -> IO (ExitCode, ByteString, ByteString) -- ^ exitcode, stdout, stderr+readCreateProcessWithExitCode = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+readProcessWithExitCode :: FilePath -> [String] -> ByteString -> IO (ExitCode, ByteString, ByteString)+readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input++-- | Like 'System.Process.readProcess', but using 'ByteString'+readProcess :: FilePath -> [String] -> ByteString -> IO ByteString+readProcess = LL.readProcess
+ src/System/Process/ByteString/Lazy.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE FlexibleContexts #-}+module System.Process.ByteString.Lazy where++import Data.ByteString.Lazy (ByteString)+import System.Exit (ExitCode)+import System.Process+import qualified System.Process.ListLike.Read as LL (readCreateProcess, readProcess)+import System.Process.ListLike.Classes (ProcessOutput)++readCreateProcess :: ProcessOutput ByteString b => CreateProcess -> ByteString -> IO b+readCreateProcess = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but takes a+-- CreateProcess instead of a command and argument list, and reads and+-- writes type 'ByteString'+readCreateProcessWithExitCode+ :: CreateProcess -- ^ command to run+ -> ByteString -- ^ standard input+ -> IO (ExitCode, ByteString, ByteString) -- ^ exitcode, stdout, stderr+readCreateProcessWithExitCode = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+readProcessWithExitCode :: FilePath -> [String] -> ByteString -> IO (ExitCode, ByteString, ByteString)+readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input++-- | Like 'System.Process.readProcess', but using 'ByteString'+readProcess :: FilePath -> [String] -> ByteString -> IO ByteString+readProcess = LL.readProcess
+ src/System/Process/Chunks.hs view
@@ -0,0 +1,230 @@+-- | Support for using the 'Chunk' list returned by 'readProcessChunks'.+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies, UndecidableInstances #-}+module System.Process.Chunks+ ( Chunk(..)+ , readCreateProcessChunks+ , discardEmptyChunks+ , fuseChunks+ , collectProcessTriple+ , collectProcessResult+ , collectProcessOutput+ , indentChunks+ , dotifyChunks+ , putChunk+ , putMappedChunks+ , putIndented+ , putIndentedShowCommand+ , putDots+ , putDotsLn+ , insertCommandStart+ , insertCommandResult+ , insertCommandDisplay+ , showCreateProcessForUser+ , showCmdSpecForUser+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.DeepSeq (NFData)+import Control.Exception (SomeException)+import Control.Monad.State (StateT, evalState, evalStateT, get, put)+import Control.Monad.Trans (lift)+import Data.ListLike (ListLike(..), ListLikeIO(hPutStr, putStr))+import Data.Monoid (Monoid, (<>), mempty)+import Data.String (IsString(fromString))+import Prelude hiding (mapM, putStr, null, tail, break, sequence, length, replicate, rem)+import System.Exit (ExitCode)+import System.IO (stderr)+import System.Process (ProcessHandle, CreateProcess(cmdspec, cwd), CmdSpec(..), showCommandForUser)+import System.Process.ListLike.Classes (ListLikeLazyIO, ProcessOutput(pidf, outf, errf, intf, codef))+import System.Process.ListLike.Read (readCreateProcess)++-- | A concrete representation of the methods in ProcessOutput.+data Chunk a+ = ProcessHandle ProcessHandle -- ^ This will always come first+ | Stdout a+ | Stderr a+ | Exception SomeException+ | Result ExitCode++instance ListLikeLazyIO a c => ProcessOutput a [Chunk a] where+ pidf p = [ProcessHandle p]+ outf x = [Stdout x]+ errf x = [Stderr x]+ intf e = [Exception e]+ codef c = [Result c]++instance ListLikeLazyIO a c => ProcessOutput a (ExitCode, [Chunk a]) where+ pidf p = (mempty, [ProcessHandle p])+ codef c = (c, mempty)+ outf x = (mempty, [Stdout x])+ errf x = (mempty, [Stderr x])+ intf e = (mempty, [Exception e])++-- | This lets us use DeepSeq's 'Control.DeepSeq.force' on a stream+-- of Chunks.+instance NFData ExitCode++-- | A concrete use of 'readCreateProcess' - build a list containing+-- chunks of process output, any exceptions that get thrown, and+-- finally an exit code. If a is a lazy type the returned list will+-- be lazy.+readCreateProcessChunks :: (ListLikeLazyIO a c) => CreateProcess -> a -> IO [Chunk a]+readCreateProcessChunks = readCreateProcess++-- | Eliminate empty Stdout or Stderr chunks.+discardEmptyChunks :: ListLikeLazyIO a c => [Chunk a] -> [Chunk a]+discardEmptyChunks [] = []+discardEmptyChunks (Stdout a : more) | null a = discardEmptyChunks more+discardEmptyChunks (Stderr a : more) | null a = discardEmptyChunks more+discardEmptyChunks (a : more) = a : discardEmptyChunks more++-- | Merge adjacent Stdout or Stderr chunks. This may be undesirable+-- if you want to get your input as soon as it becomes available, it+-- has the effect of making the result "less lazy".+fuseChunks :: ListLikeLazyIO a c => [Chunk a] -> [Chunk a]+fuseChunks [] = []+fuseChunks (Stdout a : Stdout b : more) = fuseChunks (Stdout (a <> b) : more)+fuseChunks (Stderr a : Stderr b : more) = fuseChunks (Stderr (a <> b) : more)+fuseChunks (Stdout a : more) | null a = fuseChunks more+fuseChunks (Stderr a : more) | null a = fuseChunks more+fuseChunks (a : more) = a : fuseChunks more++collectProcessTriple :: Monoid a => [Chunk a] -> (ExitCode, a, a)+collectProcessTriple [] = mempty+collectProcessTriple (Result x : xs) = (x, mempty, mempty) <> collectProcessTriple xs+collectProcessTriple (Stdout x : xs) = (mempty, x, mempty) <> collectProcessTriple xs+collectProcessTriple (Stderr x : xs) = (mempty, mempty, x) <> collectProcessTriple xs+collectProcessTriple (_ : xs) = collectProcessTriple xs++collectProcessResult :: Monoid a => [Chunk a] -> (ExitCode, [Chunk a])+collectProcessResult [] = mempty+collectProcessResult (Result x : xs) = (x, mempty) <> collectProcessResult xs+collectProcessResult (x : xs) = (mempty, [x]) <> collectProcessResult xs++collectProcessOutput :: Monoid a => [Chunk a] -> (ExitCode, a)+collectProcessOutput [] = mempty+collectProcessOutput (Result x : xs) = (x, mempty) <> collectProcessOutput xs+collectProcessOutput (Stdout x : xs) = (mempty, x) <> collectProcessOutput xs+collectProcessOutput (Stderr x : xs) = (mempty, x) <> collectProcessOutput xs+collectProcessOutput (_ : xs) = mempty <> collectProcessOutput xs++-- | Pure function to indent the text of a chunk list.+indentChunks :: forall a c. (ListLikeLazyIO a c, Eq c, IsString a) => String -> String -> [Chunk a] -> [Chunk a]+indentChunks outp errp chunks =+ evalState (Prelude.concat <$> mapM (indentChunk nl (fromString outp) (fromString errp)) chunks) BOL+ where+ nl :: c+ nl = Data.ListLike.head (fromString "\n" :: a)++-- | The monad state, are we at the beginning of a line or the middle?+data BOL = BOL | MOL deriving (Eq)++-- | Indent the text of a chunk with the prefixes given for stdout and+-- stderr. The state monad keeps track of whether we are at the+-- beginning of a line - when we are and more text comes we insert one+-- of the prefixes.+indentChunk :: forall a c m. (Monad m, Functor m, ListLikeLazyIO a c, Eq c) => c -> a -> a -> Chunk a -> StateT BOL m [Chunk a]+indentChunk nl outp errp chunk =+ case chunk of+ Stdout x -> doText Stdout outp x+ Stderr x -> doText Stderr errp x+ _ -> return [chunk]+ where+ doText :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]+ doText con pre x = do+ let (hd, tl) = break (== nl) x+ (<>) <$> doHead con pre hd <*> doTail con pre tl+ doHead :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]+ doHead _ _ x | null x = return []+ doHead con pre x = do+ bol <- get+ case bol of+ BOL -> put MOL >> return [con (pre <> x)]+ MOL -> return [con x]+ doTail :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]+ doTail _ _ x | null x = return []+ doTail con pre x = do+ bol <- get+ put BOL+ tl <- doText con pre (tail x)+ return $ (if bol == BOL then [con pre] else []) <> [con (singleton nl)] <> tl++dotifyChunks :: forall a c. (ListLikeLazyIO a c) => Int -> c -> [Chunk a] -> [Chunk a]+dotifyChunks charsPerDot dot chunks =+ evalState (Prelude.concat <$> mapM (dotifyChunk charsPerDot dot) chunks) 0++-- | dotifyChunk charsPerDot dot chunk - Replaces every charsPerDot+-- characters in the Stdout and Stderr chunks with one dot. Runs in+-- the state monad to keep track of how many characters had been seen+-- when the previous chunk finished. chunks.+dotifyChunk :: forall a c m. (Monad m, Functor m, ListLikeLazyIO a c) => Int -> c -> Chunk a -> StateT Int m [Chunk a]+dotifyChunk charsPerDot dot chunk =+ case chunk of+ Stdout x -> doChars (length x)+ Stderr x -> doChars (length x)+ _ -> return [chunk]+ where+ doChars count = do+ rem <- get+ let (count', rem') = divMod (rem + count) (fromIntegral charsPerDot)+ put rem'+ if (count' > 0) then return [Stderr (replicate count' dot)] else return []++-- | Write the Stdout chunks to stdout and the Stderr chunks to stderr.+putChunk :: ListLikeLazyIO a c => Chunk a -> IO ()+putChunk (Stdout x) = putStr x+putChunk (Stderr x) = hPutStr stderr x+putChunk _ = return ()++-- | Apply a function to the chunk list and output the result,+-- return the original (unmodified) chunk list.+putMappedChunks :: ListLikeLazyIO a c => ([Chunk a] -> [Chunk a]) -> [Chunk a] -> IO [Chunk a]+putMappedChunks f chunks = mapM_ putChunk (f chunks) >> return chunks++-- | Output the indented text of a chunk list, but return the original+-- unindented list.+putIndented :: (ListLikeLazyIO a c, Eq c, IsString a) => String -> String -> [Chunk a] -> IO [Chunk a]+putIndented outp errp chunks = putMappedChunks (indentChunks outp errp) chunks++putIndentedShowCommand :: (ListLikeLazyIO a c, Eq c, IsString a) =>+ CreateProcess -> String -> String -> [Chunk a] -> IO [Chunk a]+putIndentedShowCommand p outp errp chunks =+ putMappedChunks (insertCommandDisplay p . indentChunks outp errp) chunks++-- | Output the dotified text of a chunk list. Returns the original+-- (undotified) list.+putDots :: (ListLikeLazyIO a c) => Int -> c -> [Chunk a] -> IO [Chunk a]+putDots charsPerDot dot chunks =+ evalStateT (mapM (\ x -> dotifyChunk charsPerDot dot x >>= mapM_ (lift . putChunk) >> return x) chunks) 0++-- | Output the dotified text of a chunk list with a newline at EOF.+-- Returns the original list.+putDotsLn :: forall a c. (IsString a, ListLikeLazyIO a c) => Int -> c -> [Chunk a] -> IO [Chunk a]+putDotsLn cpd dot chunks = putDots cpd dot chunks >>= \ r -> hPutStr stderr (fromString "\n" :: a) >> return r++-- | Insert a chunk displaying the command and its arguments at the+-- beginning of the chunk list.+insertCommandStart :: (IsString a, ListLikeLazyIO a c, Eq c) =>+ CreateProcess -> [Chunk a] -> [Chunk a]+insertCommandStart p chunks = [Stderr (fromString (" -> " ++ showCreateProcessForUser p ++ "\n"))] <> chunks++-- | Insert a chunk displaying the command and the result code.+insertCommandResult :: (IsString a, ListLikeLazyIO a c, Eq c) =>+ CreateProcess -> [Chunk a] -> [Chunk a]+insertCommandResult _ [] = []+insertCommandResult p (Result code : xs) =+ Stderr (fromString (" <- " ++ show code ++ " <- " ++ showCmdSpecForUser (cmdspec p) ++ "\n")) :+ Result code :+ xs+insertCommandResult p (x : xs) = x : insertCommandResult p xs++insertCommandDisplay :: (IsString a, ListLikeLazyIO a c, Eq c) => CreateProcess -> [Chunk a] -> [Chunk a]+insertCommandDisplay p = insertCommandResult p . insertCommandStart p++showCreateProcessForUser :: CreateProcess -> String+showCreateProcessForUser p =+ showCmdSpecForUser (cmdspec p) ++ maybe "" (\ d -> " (in " ++ d ++ ")") (cwd p)++showCmdSpecForUser :: CmdSpec -> String+showCmdSpecForUser (ShellCommand s) = s+showCmdSpecForUser (RawCommand p args) = showCommandForUser p args
+ src/System/Process/ListLike.hs view
@@ -0,0 +1,8 @@+module System.Process.ListLike+ ( module System.Process.ListLike.Classes+ , module System.Process.ListLike.Read+ ) where++import System.Process.ListLike.Classes+import System.Process.ListLike.Instances ()+import System.Process.ListLike.Read
+ src/System/Process/ListLike/Classes.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE FunctionalDependencies, MultiParamTypeClasses #-}+module System.Process.ListLike.Classes+ ( ListLikeLazyIO(..)+ , ProcessOutput(..)+ ) where++import Control.Exception (SomeException)+import Data.ListLike (ListLikeIO(..))+import Data.ListLike.Text.Text ()+import Data.ListLike.Text.TextLazy ()+import Data.Monoid (Monoid(mempty, mappend))+import Prelude hiding (null, length, rem)+import System.Exit (ExitCode(ExitFailure))+import System.IO hiding (hPutStr, hGetContents)+import System.Process (ProcessHandle)++-- | Methods for turning the output of a process into a monoid.+class Monoid b => ProcessOutput a b | b -> a where+ pidf :: ProcessHandle -> b+ outf :: a -> b+ errf :: a -> b+ intf :: SomeException -> b+ codef :: ExitCode -> b++-- | A process usually has one 'ExitCode' at the end of its output, this 'Monoid'+-- instance lets us build the type returned by 'System.Process.readProcessWithExitCode'.+instance Monoid ExitCode where+ mempty = ExitFailure 0+ mappend x (ExitFailure 0) = x+ mappend _ x = x++-- | Class of types which can be used as the input and outputs of+-- these process functions.+class ListLikeIO a c => ListLikeLazyIO a c where+ readChunks :: Handle -> IO [a]+ -- ^ Read the list of chunks from this handle. For lazy types this+ -- is just a call to 'hGetContents' followed by 'toChunks'. For strict+ -- types it might return a singleton list. Strings are trickier.
+ src/System/Process/ListLike/Instances.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies, TypeSynonymInstances, UndecidableInstances #-}++-- | ListLikeLazyIO instances for strict and lazy types. If you start a+-- long running process with a strict type it will block until the+-- process finishes. Why not try a lazy type?++{-# OPTIONS_GHC -fno-warn-orphans #-}+module System.Process.ListLike.Instances where++import Control.DeepSeq (force)+import Control.Exception as E (evaluate, throw)+import Data.ByteString.Char8 as B (ByteString)+import qualified Data.ByteString.Lazy as L+import Data.ListLike.IO (hGetContents)+import Data.Monoid (mempty)+import Data.Text as T (Text)+import qualified Data.Text.Lazy as LT+import Data.Word (Word8)+import System.Exit (ExitCode)+import System.Process.ListLike.Classes (ListLikeLazyIO(..), ProcessOutput(..))++instance ListLikeLazyIO B.ByteString Word8 where+ --setModes _ (inh, outh, errh, _) = f inh >> f outh >> f errh where f = maybe (return ()) (\ h -> hSetBinaryMode h True)+ readChunks h = hGetContents h >>= return . force . (: [])++instance ListLikeLazyIO T.Text Char where+ --setModes _ _ = return ()+ readChunks h = hGetContents h >>= return . force . (: [])++instance ListLikeLazyIO L.ByteString Word8 where+ --setModes _ (inh, outh, errh, _) = f inh >> f outh >> f errh where f = maybe (return ()) (\ h -> hSetBinaryMode h True)+ readChunks h = hGetContents h >>= evaluate . Prelude.map (L.fromChunks . (: [])) . L.toChunks++instance ListLikeLazyIO LT.Text Char where+ --setModes _ _ = return ()+ readChunks h = hGetContents h >>= evaluate . Prelude.map (LT.fromChunks . (: [])) . LT.toChunks++instance ListLikeLazyIO a c => ProcessOutput a (ExitCode, a, a) where+ pidf _ = mempty+ codef c = (c, mempty, mempty)+ outf x = (mempty, x, mempty)+ errf x = (mempty, mempty, x)+ intf e = throw e
+ src/System/Process/ListLike/LazyString.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances #-}+module System.Process.ListLike.LazyString where++import Data.Text as T (unpack)+import Data.Text.Lazy as LT (toChunks)+import System.Process.ListLike.Classes (ListLikeLazyIO(readChunks))+import System.Process.ListLike.Instances ()++-- | This String instance is implemented using the Lazy Text instance.+-- Otherwise (without some serious coding) String would be a strict+-- instance . Note that the 'System.Process.readProcess' in the+-- process library is strict, while our equivalent is not - see test4+-- in Tests/Dots.hs.+instance ListLikeLazyIO String Char where+ readChunks h = readChunks h >>= return . map T.unpack . concat . map LT.toChunks
+ src/System/Process/ListLike/Read.hs view
@@ -0,0 +1,165 @@+-- | Generalized versions of the functions+-- 'System.Process.readProcess', and+-- 'System.Process.readProcessWithExitCode'.+{-# LANGUAGE FlexibleContexts, FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, ScopedTypeVariables, TupleSections, TypeFamilies, UndecidableInstances #-}+module System.Process.ListLike.Read+ ( readCreateProcess,+ readCreateProcess',+ readInterleaved,+ readCreateProcessWithExitCode,+ readProcessWithExitCode,+ StdoutWrapper(..),+ readProcess+ ) where++import Control.Applicative ((<$>), (<*>), pure)+import Control.Concurrent+import Control.Exception as E (SomeException, onException, catch, mask, throw)+import Control.Monad+import Data.ListLike (ListLike(..), ListLikeIO(..))+import Data.ListLike.Text.Text ()+import Data.ListLike.Text.TextLazy ()+import Data.Maybe (maybeToList)+import Data.Monoid (Monoid(mempty, mappend), (<>))+import GHC.IO.Exception (IOErrorType(OtherError, ResourceVanished), IOException(ioe_type))+import Prelude hiding (null, length, rem)+import System.Exit (ExitCode(ExitSuccess))+import System.IO hiding (hPutStr, hGetContents)+import qualified System.IO.Error as IO+import System.IO.Unsafe (unsafeInterleaveIO)+import System.Process (CreateProcess(..), StdStream(CreatePipe, Inherit), proc,+ createProcess, waitForProcess, terminateProcess)+import System.Process.ListLike.Classes (ListLikeLazyIO(..), ProcessOutput(..))+import System.Process.ListLike.Instances ()+import Utils (forkWait)++-- | Read the output of a process and use the argument functions to+-- convert it into a Monoid, preserving the order of appearance of the+-- different chunks of output from standard output and standard error.+readCreateProcess :: (ListLikeLazyIO a c, ProcessOutput a b) => CreateProcess -> a -> IO b+readCreateProcess p input =+ readCreateProcess' (p {std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe }) input++readCreateProcess' :: (ListLikeLazyIO a c, ProcessOutput a b) => CreateProcess -> a -> IO b+readCreateProcess' p input = mask $ \ restore -> do+ (Just inh, maybe_outh, maybe_errh, pid) <- createProcess p++ onException+ (restore $+ do -- Without unsafeIntereleaveIO the pid messsage gets stuck+ -- until some additional output arrives from the process.+ waitOut <- forkWait $ (<>) <$> pure (pidf pid)+ <*> unsafeInterleaveIO (readInterleaved (maybeToList (fmap (outf,) maybe_outh) <> maybeToList (fmap (errf,) maybe_errh))+ (codef <$> waitForProcess pid))+ writeInput inh input+ waitOut)+ (do terminateProcess pid+ hClose inh+ maybe (return ()) hClose maybe_outh+ maybe (return ()) hClose maybe_errh+ waitForProcess pid)++-- | Simultaneously read the output from several file handles, using+-- the associated functions to add them to a Monoid b in the order+-- they appear. This closes each handle on EOF, because AFAIK it is+-- the only useful thing to do with a file handle that has reached+-- EOF.+readInterleaved :: forall a b c. (ListLikeLazyIO a c, ProcessOutput a b) =>+ [(a -> b, Handle)] -> IO b -> IO b+readInterleaved pairs finish = newEmptyMVar >>= readInterleaved' pairs finish++readInterleaved' :: forall a b c. (ListLikeLazyIO a c, ProcessOutput a b) =>+ [(a -> b, Handle)] -> IO b -> MVar (Either Handle b) -> IO b+readInterleaved' pairs finish res = do+ mapM_ (forkIO . uncurry readHandle) pairs+ takeChunks (length pairs)+ where+ -- Forked thread to read the input and send it to takeChunks via+ -- the MVar.+ readHandle :: (a -> b) -> Handle -> IO ()+ readHandle f h = do+ cs <- readChunks h+ -- If the type returned as stdout and stderr is lazy we need+ -- to force it here in the producer thread - I'm not exactly+ -- sure why. And why is String lazy?+ -- when (lazy (undefined :: a)) (void cs)+ mapM_ (\ c -> putMVar res (Right (f c))) cs+ hClose h+ putMVar res (Left h)+ takeChunks :: Int -> IO b+ takeChunks 0 = finish+ takeChunks openCount = takeChunk >>= takeMore openCount+ takeMore :: Int -> Either Handle b -> IO b+ takeMore openCount (Left h) = hClose h >> takeChunks (openCount - 1)+ takeMore openCount (Right x) =+ do xs <- unsafeInterleaveIO $ takeChunks openCount+ return (x <> xs)+ takeChunk = takeMVar res `catch` (\ (e :: SomeException) -> return $ Right $ intf e)++-- | An implementation of 'System.Process.readProcessWithExitCode'+-- with a two generalizations: (1) The input and outputs can be any+-- instance of 'ListLikeLazyIO', and (2) The CreateProcess is passes an+-- argument, so you can use either 'System.Process.proc' or+-- 'System.Process.rawSystem' and you can modify its fields such as+-- 'System.Process.cwd' before the process starts+readCreateProcessWithExitCode :: ListLikeLazyIO a c =>+ CreateProcess -- ^ process to run+ -> a -- ^ standard input+ -> IO (ExitCode, a, a) -- ^ exitcode, stdout, stderr+readCreateProcessWithExitCode p input = readCreateProcess p input++-- | A version of 'System.Process.readProcessWithExitCode' that uses+-- any instance of 'ListLikeLazyIO' instead of 'String', implemented+-- using 'readCreateProcessWithExitCode'.+readProcessWithExitCode :: ListLikeLazyIO a c =>+ FilePath -- ^ command to run+ -> [String] -- ^ any arguments+ -> a -- ^ standard input+ -> IO (ExitCode, a, a) -- ^ exitcode, stdout, stderr+readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input++-- | Implementation of 'System.Process.readProcess' that uses any+-- instance of 'ListLikeLazyIO' instead of 'String', implemented using+-- 'readCreateProcess'. As with 'System.Process.readProcess', Stderr+-- goes directly to the console, only stdout is returned. Also like+-- 'System.Process.readProcess', an IO error of type OtherError is+-- thrown when the result code is not ExitSuccess.+readProcess :: ListLikeLazyIO a c =>+ FilePath -- ^ command to run+ -> [String] -- ^ any arguments+ -> a -- ^ standard input+ -> IO a -- ^ stdout+readProcess cmd args input =+ unStdoutWrapper <$> readCreateProcess' ((proc cmd args) {std_in = CreatePipe, std_out = CreatePipe, std_err = Inherit}) input++-- | For the 'readProcess' function, we need to wrap a newtype around+-- the output type so we can build a ProcessOutput instance for it.+-- Otherwise it would overlap everything.+newtype StdoutWrapper a = StdoutWrapper {unStdoutWrapper :: a}++instance Monoid a => Monoid (StdoutWrapper a) where+ mempty = StdoutWrapper mempty+ mappend (StdoutWrapper a) (StdoutWrapper b) = StdoutWrapper (a <> b)++instance (ListLikeLazyIO a c, Monoid a) => ProcessOutput a (StdoutWrapper a) where+ pidf _ = mempty+ codef ExitSuccess = mempty+ codef failure = throw $ IO.mkIOError OtherError ("Process exited with " ++ show failure) Nothing Nothing+ outf x = StdoutWrapper x+ errf _ = mempty+ intf e = throw e++-- | Write and flush process input, closing the handle when done.+-- Catch and ignore Resource Vanished exceptions, they just mean the+-- process exited before all of its output was read.+writeInput :: ListLikeLazyIO a c => Handle -> a -> IO ()+writeInput inh input = do+ (do unless (null input) (hPutStr inh input >> hFlush inh)+ hClose inh) `E.catch` resourceVanished (\ _ -> 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
+ src/System/Process/ListLike/ReadNoThreads.hs view
@@ -0,0 +1,173 @@+{-# LANGUAGE MultiParamTypeClasses, PackageImports, ScopedTypeVariables #-}+{-# OPTIONS -Wwarn -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures #-}++-- | This is an alternative version of the module+-- "System.Process.ListLike.Read" that doesn't use forkIO. I don't+-- know of any advantages, I'm just including it because it is derived+-- from old code I had developed, it is kinda badass, and maybe+-- someone has a use for it.+--+-- Function to run a process and return a lazy list of chunks from+-- standard output, standard error, and at the end of the list an+-- object indicating the process result code. If neither output+-- handle is ready for reading the process sleeps and tries again,+-- with the sleep intervals increasing from 8 microseconds to a+-- maximum of 0.1 seconds.+module System.Process.ListLike.ReadNoThreads+ ( ListLikeIOPlus(..)+ , readCreateProcess+ , readCreateProcessWithExitCode+ , readProcessWithExitCode+ ) where++import Control.Applicative ((<$>), (<*>), pure)+import Control.Concurrent (threadDelay)+import Control.Exception (catch, mask, onException, try)+import Data.ListLike (ListLike(length, null), ListLikeIO(hGetNonBlocking))+import Data.Maybe (mapMaybe)+import Data.Monoid (Monoid(mempty), (<>), mconcat)+import qualified Data.Text.Lazy as LT+import Data.Text.Lazy.Encoding (encodeUtf8)+import qualified GHC.IO.Exception as E+import Prelude hiding (length, null)+import System.Exit (ExitCode)+import System.Process (ProcessHandle, CreateProcess(..), waitForProcess, proc, createProcess, StdStream(CreatePipe), terminateProcess)+import System.IO (Handle, hReady, hClose)+import System.IO.Unsafe (unsafeInterleaveIO)+import System.Process.ListLike.Classes (ProcessOutput(..), ListLikeLazyIO(..))+import System.Process.ListLike.Instances ()++-- For the ListLikeIOPlus instance+import qualified Data.ByteString.Lazy as L+import Data.Word (Word8)++-- | For the unthreaded implementation we need a more powerful+-- ListLikeLazyIO class.+class ListLikeLazyIO a c => ListLikeIOPlus a c where+ hPutNonBlocking :: Handle -> a -> IO a+ chunks :: a -> [a]++instance ListLikeIOPlus L.ByteString Word8 where+ hPutNonBlocking = L.hPutNonBlocking+ chunks = Prelude.map (L.fromChunks . (: [])) . L.toChunks++instance ListLikeIOPlus LT.Text Char where+ hPutNonBlocking h text = L.hPutNonBlocking h (encodeUtf8 text) >> return text+ chunks = map (LT.fromChunks . (: [])) . LT.toChunks++bufSize = 65536 -- maximum chunk size+uSecs = 8 -- minimum wait time, doubles each time nothing is ready+maxUSecs = 100000 -- maximum wait time (microseconds)++readCreateProcess :: forall a b c. (ListLikeIOPlus a c, ProcessOutput a b) => CreateProcess -> a -> IO b+readCreateProcess p input = mask $ \ restore -> do+ (Just inh, Just outh, Just errh, pid) <-+ createProcess (p {std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe})++ -- setModes input hs++ -- The uses of unsafeInterleaveIO here and below are required to+ -- keep the output from blocking waiting for process exit.+ onException+ (restore $ (<>) <$> pure (pidf pid)+ <*> (unsafeInterleaveIO $ elements pid (chunks input, Just inh, [(outf, outh), (errf, errh)], Nothing)))+ (do terminateProcess pid; hClose inh; hClose outh; hClose errh;+ waitForProcess pid)+ where+ elements :: ProcessHandle -> ([a], Maybe Handle, [(a -> b, Handle)], Maybe b) -> IO b+ -- EOF on both output descriptors, get exit code. It can be+ -- argued that the list 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 nowhere else is a Result added. However, the process+ -- doing the traversing might die before that end is reached.+ elements pid tuple@(_, _, [], elems) =+ do result <- try (waitForProcess pid)+ case result of+ Left exn -> (<>) <$> pure (maybe mempty id elems <> intf exn) <*> elements pid tuple+ Right code -> pure (maybe mempty id elems <> codef code)+ -- The available output has been processed, send input and read+ -- from the ready handles+ elements pid tuple@(_, _, _, Nothing) =+ do result <- try (ready uSecs tuple)+ case result of+ Left exn -> (<>) <$> pure (intf exn) <*> elements pid tuple+ Right tuple' -> elements pid tuple'+ -- Add some output to the result value+ elements pid (input, inh, pairs, Just elems) =+ (<>) <$> pure elems+ <*> (unsafeInterleaveIO $ elements pid (input, inh, pairs, Nothing))++-- A quick fix for the issue where hWaitForInput has actually started+-- raising the isEOFError exception in ghc 6.10.+data Readyness = Ready | Unready | EndOfFile deriving Eq++hReady' :: Handle -> IO Readyness+hReady' h = (hReady h >>= (\ flag -> return (if flag then Ready else Unready))) `catch` (\ (e :: IOError) ->+ case E.ioe_type e of+ E.EOF -> return EndOfFile+ _ -> error (show e))++-- | Wait until at least one handle is ready and then write input or+-- read output. Note that there is no way to check whether the input+-- handle is ready except to try to write to it and see if any bytes+-- are accepted. If no input is accepted, or the input handle is+-- already closed, and none of the output descriptors are ready for+-- reading the function sleeps and tries again.+ready :: (ListLikeIOPlus a c, ProcessOutput a b) =>+ Int -> ([a], Maybe Handle, [(a -> b, Handle)], Maybe b)+ -> IO ([a], Maybe Handle, [(a -> b, Handle)], Maybe b)+ready waitUSecs (input, inh, pairs, elems) =+ do+ anyReady <- mapM (hReady' . snd) pairs+ case (input, inh, anyReady) of+ -- Input exhausted, close the input handle.+ ([], Just handle, _) | all (== Unready) anyReady ->+ do hClose handle+ ready waitUSecs ([], Nothing, pairs, elems)+ -- Input handle closed and there are no ready output handles,+ -- wait a bit+ ([], Nothing, _) | all (== Unready) anyReady ->+ do threadDelay waitUSecs+ --ePut0 ("Slept " ++ show uSecs ++ " microseconds\n")+ ready (min maxUSecs (2 * waitUSecs)) (input, inh, pairs, elems)+ -- Input is available and there are no ready output handles+ (input : etc, Just handle, _)+ -- Discard a zero byte input+ | all (== Unready) anyReady && null input -> ready waitUSecs (etc, inh, pairs, elems)+ -- Send some input to the process+ | all (== Unready) anyReady ->+ do input' <- hPutNonBlocking handle input+ case null input' of+ -- Input buffer is full too, sleep.+ True -> do threadDelay uSecs+ ready (min maxUSecs (2 * waitUSecs)) (input : etc, inh, pairs, elems)+ -- We wrote some input, discard it and continue+ False -> return (input' : etc, Just handle, pairs, elems)+ -- One or both output handles are ready, try to read from them+ _ ->+ do allOutputs <- mapM (\ ((f, h), r) -> nextOut h r f) (zip pairs anyReady)+ let newPairs = mapMaybe (\ ((_, mh), (f, _)) -> maybe Nothing (\ h -> Just (f, h)) mh) (zip allOutputs pairs)+ return (input, inh, newPairs, elems <> mconcat (map fst allOutputs))++-- | Return the next output element and the updated handle+-- from a handle which is assumed ready.+nextOut :: (ListLikeIO a c, ProcessOutput a b) => Handle -> Readyness -> (a -> b) -> IO (Maybe b, Maybe Handle)+nextOut _ EndOfFile _ = return (Nothing, Nothing) -- Handle is closed+nextOut handle Unready _ = return (Nothing, Just handle) -- Handle is not ready+nextOut handle Ready constructor = -- Perform a read+ do+ a <- hGetNonBlocking handle bufSize+ case length a of+ -- A zero length read, unlike a zero length write, always+ -- means EOF.+ 0 -> do hClose handle+ return (Nothing, Nothing)+ -- Got some input+ _n -> return (Just (constructor a), Just handle)++readCreateProcessWithExitCode :: (ListLikeIOPlus a c) => CreateProcess -> a -> IO (ExitCode, a, a)+readCreateProcessWithExitCode p input = readCreateProcess p input++readProcessWithExitCode :: (ListLikeIOPlus a c) => FilePath -> [String] -> a -> IO (ExitCode, a, a)+readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input
+ src/System/Process/ListLike/StrictString.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances #-}+module System.Process.ListLike.StrictString where++import Control.DeepSeq (force)+import Data.ListLike (hGetContents)+import System.Process.ListLike.Classes (ListLikeLazyIO(readChunks))++instance ListLikeLazyIO String Char where+ readChunks h = hGetContents h >>= return . force . (: [])
+ src/System/Process/String.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE FlexibleContexts #-}+module System.Process.String where++import System.Exit (ExitCode)+import System.Process (CreateProcess(..), CmdSpec(..), readProcessWithExitCode)+import System.Process.ListLike.Classes (ProcessOutput, ListLikeLazyIO)+import System.Process.ListLike.Read as LL (readCreateProcess)++-- No ListLikeLazyIO instance is imported here because we have two and+-- naturally they conflict.+readCreateProcess :: (ListLikeLazyIO String Char, ProcessOutput String b) => CreateProcess -> String -> IO b+readCreateProcess = LL.readCreateProcess++readCreateProcessWithExitCode+ :: ListLikeLazyIO String Char =>+ CreateProcess -- ^ command to run+ -> String -- ^ standard input+ -> IO (ExitCode, String, String) -- ^ exitcode, stdout, stderr+readCreateProcessWithExitCode = LL.readCreateProcess
+ src/System/Process/Text.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE FlexibleContexts #-}+module System.Process.Text where++import Data.Text (Text)+import System.Process+import System.Process.ListLike.Classes (ProcessOutput)+import qualified System.Process.ListLike.Read as LL (readCreateProcess, readProcess)+import System.Exit (ExitCode)++readCreateProcess :: ProcessOutput Text b => CreateProcess -> Text -> IO b+readCreateProcess = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but takes a+-- CreateProcess instead of a command and argument list, and reads and+-- writes type 'ByteString'+readCreateProcessWithExitCode+ :: CreateProcess -- ^ command to run+ -> Text -- ^ standard input+ -> IO (ExitCode, Text, Text) -- ^ exitcode, stdout, stderr+readCreateProcessWithExitCode = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+readProcessWithExitCode :: FilePath -> [String] -> Text -> IO (ExitCode, Text, Text)+readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input++-- | Like 'System.Process.readProcess', but using 'Text'+readProcess :: FilePath -> [String] -> Text -> IO Text+readProcess = LL.readProcess
+ src/System/Process/Text/Lazy.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE FlexibleContexts #-}+module System.Process.Text.Lazy where++import Data.Text.Lazy (Text)+import System.Exit (ExitCode)+import System.Process+import System.Process.ListLike.Classes (ProcessOutput)+import qualified System.Process.ListLike.Read as LL (readCreateProcess, readProcess)++readCreateProcess :: ProcessOutput Text b => CreateProcess -> Text -> IO b+readCreateProcess = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but using 'Text'+readCreateProcessWithExitCode+ :: CreateProcess -- ^ command to run+ -> Text -- ^ standard input+ -> IO (ExitCode, Text, Text) -- ^ exitcode, stdout, stderr+readCreateProcessWithExitCode = LL.readCreateProcess++-- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+readProcessWithExitCode :: FilePath -> [String] -> Text -> IO (ExitCode, Text, Text)+readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input++-- | Like 'System.Process.readProcess', but using 'Text'+readProcess :: FilePath -> [String] -> Text -> IO Text+readProcess = LL.readProcess
+ src/Utils.hs view
@@ -0,0 +1,10 @@+module Utils where++import Control.Concurrent+import Control.Exception++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)
+ tests/Interactive.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE ScopedTypeVariables, StandaloneDeriving #-}+-- | Tests that require a user to send a keyboard interrupt.+module Main where++import Control.Exception (SomeException)+import qualified Data.Text.Lazy+import qualified Data.ByteString.Lazy+import Data.Monoid+import System.Process+import System.Process.Chunks (Chunk(..))+import System.Process.ListLike.Instances ()+import qualified System.Process.ListLike.ReadNoThreads as NoThreads+import qualified System.Process.ListLike.Read as Threads+import System.Environment+import System.IO+import Text.Read (readMaybe)++instance Show ProcessHandle where+ show _ = "<processhandle>"++instance Eq ProcessHandle where+ _ == _ = False++instance Eq SomeException where+ _ == _ = False++deriving instance Show a => Show (Chunk a)++deriving instance Eq a => Eq (Chunk a)++main :: IO ()+main = do+ args <- getArgs+ case readArgs args of+ Nothing ->+ hPutStrLn stderr $+ unlines ([ "usage: interactive-tests test# runner#"+ , "tests: " ] +++ map (\ (n, test) -> " " ++ show n ++ ". " ++ test) (zip ([1..] :: [Int]) tests) +++ [ "runners: " ] +++ map (\ (n, (runner, _)) -> " " ++ show n ++ ". " ++ runner) (zip ([1..] :: [Int]) runners))+ Just (test, (s, runner)) ->+ do hPutStrLn stderr (test ++ " -> " ++ s)+ runner (shell test)+ where+ readArgs :: [String] -> Maybe (String, (String, CreateProcess -> IO ()))+ readArgs [t, r] =+ case (readMaybe t, readMaybe r) of+ (Just t', Just r')+ | t' >= 1 && t' <= length tests && r' >= 1 && r' <= length runners ->+ Just (tests !! (t' - 1), runners !! (r' - 1))+ _ -> Nothing+ readArgs _ = Nothing++tests :: [String]+tests = [ "ls -l /tmp"+ , "yes | cat -n | while read i; do echo $i; sleep 1; done"+ , "oneko"+ , "yes | cat -n" ]++runners :: [(String, CreateProcess -> IO ())]+runners = [ ("NoThreads.readCreateProcess Lazy.Text",+ \ p -> NoThreads.readCreateProcess p mempty >>= \ (b :: [Chunk Data.Text.Lazy.Text]) ->+ mapM_ (hPutStrLn stderr . show) b)+ , ("NoThreads.readCreateProcess Lazy.ByteString",+ \ p -> NoThreads.readCreateProcess p mempty >>= \ (b :: [Chunk Data.ByteString.Lazy.ByteString]) ->+ mapM_ (hPutStrLn stderr . show) b)+ , ("Threads.readCreateProcess Lazy.Text",+ \ p -> Threads.readCreateProcess p mempty >>= \ (b :: [Chunk Data.Text.Lazy.Text]) ->+ mapM_ (hPutStrLn stderr . show) b)+ , ("Threads.readCreateProcess Lazy.ByteString",+ \ p -> Threads.readCreateProcess p mempty >>= \ (b :: [Chunk Data.ByteString.Lazy.ByteString]) ->+ mapM_ (hPutStrLn stderr . show) b)+ ]
+ tests/Main.hs view
@@ -0,0 +1,252 @@+{-# LANGUAGE CPP, FlexibleInstances, OverloadedStrings, RankNTypes, ScopedTypeVariables, StandaloneDeriving #-}+module Main where++import Codec.Binary.UTF8.String (encode)+import Control.Applicative ((<$>))+import Control.DeepSeq (NFData, force)+import Control.Exception (SomeException, try)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import Data.Char (chr, ord)+import Data.List (isSuffixOf)+import Data.ListLike as ListLike (ListLike(length, concat, null, groupBy, head, toList, dropWhile, takeWhile, drop, length), ListLikeIO(readFile))+import Data.Maybe (mapMaybe)+import Data.Monoid (Monoid(..), (<>))+import Data.String (IsString(fromString))+import qualified Data.Text as T+import qualified Data.Text.Lazy as LT+import Prelude hiding (length, readFile, head)+import GHC.IO.Exception+import System.Exit+import System.IO (hPutStrLn, stderr)+import System.Posix.Files (getFileStatus, fileMode, setFileMode, unionFileModes, ownerExecuteMode, groupExecuteMode, otherExecuteMode)+import System.Process (CreateProcess, proc, ProcessHandle, shell, readProcessWithExitCode)+import System.Process.ByteString as B+import System.Process.ByteString.Lazy as LB+import System.Process.String as S+import System.Process.Text as T+import System.Process.Text.Lazy as LT+import System.Process.Chunks as Chunks+import System.Process.ListLike.Classes (ListLikeLazyIO, ProcessOutput)+import System.Process.ListLike.Instances+import System.Process.ListLike.StrictString () -- the lazy one is the same as lazy text, the strict one is more interesting to test+import System.Process.ListLike.Read as LL+import System.Timeout+import Test.HUnit hiding (path)+import Text.Regex.Posix ((=~))+import Text.Printf++instance Show ProcessHandle where+ show _ = "<processhandle>"++instance Eq ProcessHandle where+ _ == _ = False++instance Eq SomeException where+ _ == _ = False++deriving instance Show a => Show (Chunk a)++deriving instance Eq a => Eq (Chunk a)++instance Monoid Test where+ mempty = TestList []+ mappend (TestList a) (TestList b) = TestList (a <> b)+ mappend (TestList a) b = TestList (a <> [b])+ mappend a (TestList b) = TestList ([a] <> b)+ mappend a b = TestList [a, b]++type MkTest = forall a c. (Show a, ListLikeLazyIO a c, IsString a, Eq a, Enum c) => String -> a -> Test++testInstances :: MkTest -> Test+testInstances mkTest = mappend (testCharInstances mkTest) (testWord8Instances mkTest)++testStrictInstances :: MkTest -> Test+testStrictInstances mkTest = mappend (testStrictCharInstances mkTest) (testStrictWord8Instances mkTest)++testLazyInstances :: MkTest -> Test+testLazyInstances mkTest = mappend (testLazyCharInstances mkTest) (testLazyWord8Instances mkTest)++testCharInstances :: MkTest -> Test+testCharInstances mkTest = mappend (testLazyCharInstances mkTest) (testStrictCharInstances mkTest)++testLazyCharInstances :: MkTest -> Test+testLazyCharInstances mkTest = mkTest "Lazy Text" LT.empty++testStrictCharInstances :: MkTest -> Test+testStrictCharInstances mkTest = mappend (mkTest "Strict Text" T.empty) (mkTest "String" ("" :: String))++testWord8Instances :: MkTest -> Test+testWord8Instances mkTest = mappend (testLazyWord8Instances mkTest) (testStrictWord8Instances mkTest)++testLazyWord8Instances :: MkTest -> Test+testLazyWord8Instances mkTest = mkTest "Lazy ByteString" LB.empty++testStrictWord8Instances :: MkTest -> Test+testStrictWord8Instances mkTest = mkTest "Strict ByteString" B.empty++main :: IO ()+main =+ do chmod "tests/script1.hs"+ chmod "tests/script4.hs"+ (c,st) <- runTestText putTextToShowS test1 -- (TestList (versionTests ++ sourcesListTests ++ dependencyTests ++ changesTests))+ putStrLn (st "")+ case (failures c) + (errors c) of+ 0 -> return ()+ _ -> exitFailure++chmod :: FilePath -> IO ()+chmod path =+ getFileStatus path >>= \ status ->+ setFileMode path (foldr unionFileModes (fileMode status) [ownerExecuteMode, groupExecuteMode, otherExecuteMode])++cps :: [CreateProcess]+cps = [ proc "true" []+ , proc "false" []+ , shell "foo"+ , proc "foo" []+ , shell "yes | cat -n | head 100"+ , shell "yes | cat -n"+ , proc "cat" ["tests/text"]+ , proc "cat" ["tests/houseisclean.jpg"]+ , proc "tests/script1.hs" []+ ]++test1 :: Test+test1 =+ TestLabel "test1"+ (TestList+ [ TestLabel "[Output]" $+ TestList+ [ testInstances+ (\ s i -> TestLabel s $ TestCase $ do -- We can use a string for the input because the process I/O types+ -- are instances of IsString.+ result <- LL.readCreateProcessWithExitCode (proc "tests/script4.hs" []) ("a" <> i)+ assertEqual "file closed 1" (ExitSuccess, "a", "Read one character: 'a'\n") result)+ , testInstances+ (\ s i -> TestLabel s $ TestCase $ do+ result <- LL.readCreateProcessWithExitCode (proc "tests/script4.hs" []) i+ assertEqual "file closed 2" (ExitFailure 1, "", "script4.hs: <stdin>: hGetChar: end of file\n") result)+ , testInstances+ (\ s i -> TestLabel s $ TestCase $ do+ result <- LL.readCreateProcessWithExitCode (proc "tests/script4.hs" []) ("abcde" <> i)+ assertEqual "file closed 3" (ExitSuccess, "a", "Read one character: 'a'\n") result)+ , testCharInstances+ (\ s i -> TestLabel s $ TestCase $ do+ b <- LL.readCreateProcessWithExitCode (proc "cat" ["tests/text"]) i+ assertEqual+ "Unicode"+ (ExitSuccess,+ -- For Text, assuming your locale is set to utf8, the result is unicode.+ "Signed: Baishi laoren \30333\30707\32769\20154, painted in the artist\8217s 87th year.\n",+ "")+ b)+ , testWord8Instances+ (\ s i -> TestLabel s $ TestCase $ do+ b <- LL.readCreateProcessWithExitCode (proc "cat" ["tests/text"]) i+ assertEqual+ "UTF8"+ (ExitSuccess,+ -- For ByteString we get utf8 encoded text+ "Signed: Baishi laoren \231\153\189\231\159\179\232\128\129\228\186\186, painted in the artist\226\128\153s 87th year.\n",+ "")+ b)+ , testInstances+ (\ s i -> TestLabel s $ TestCase $ do+ b <- LL.readCreateProcessWithExitCode (proc "tests/script1.hs" []) i+ assertEqual+ "ExitFailure"+ (ExitFailure 123,+ "",+ "This is an error message.\n")+ b)+ , testWord8Instances+ (\ s i -> TestLabel s $ TestCase $ do+ (ex, out, err) <- LL.readCreateProcessWithExitCode (proc "cat" ["tests/houseisclean.jpg"]) i+ assertEqual "Length" (ExitSuccess, 68668, 0) (ex, length out, length err))+ , testInstances+ (\ s i -> TestLabel s $ TestCase $ do+ l <- LL.readCreateProcessWithExitCode (proc "tests/script1.hs" []) i+ assertEqual "Error 123" (ExitFailure 123, "", "This is an error message.\n") l)+ , testWord8Instances+ (\ s i -> TestLabel s $ TestCase $ do+ jpg <- readFile "tests/penguin.jpg"+ (code1, pnm, err1) <- LL.readCreateProcessWithExitCode (proc "djpeg" []) (i <> jpg) -- force the return type of readFile to match i+ (code2, out2, err2) <- LL.readCreateProcessWithExitCode (proc "pnmfile" []) pnm+ assertEqual "pnmfile1"+ (ExitSuccess, mempty, 2192, 27661, "stdin:\tPPM raw, 96 by 96 maxval 255\n")+ (code1, err1, length jpg, length pnm, out2))+ , testCharInstances+ (\ s i -> TestLabel s $ TestCase $ do+ Left e <- try $ do jpg <- readFile "tests/penguin.jpg"+ (code1, pnm, err1) <- LL.readCreateProcessWithExitCode (proc "djpeg" []) (i <> jpg)+ LL.readCreateProcessWithExitCode (proc "pnmfile" []) pnm+ assertEqual "pnmfile2" (IOError {ioe_handle = ioe_handle e,+ ioe_type = InvalidArgument,+ ioe_location = "hGetContents",+ ioe_description = "invalid byte sequence",+ ioe_errno = Nothing,+ ioe_filename = Just "tests/penguin.jpg"})+ (e :: IOError))+ , testInstances+ (\ s i -> TestLabel s $ TestCase $ do+ let n = case isSuffixOf "ByteString" s of+ True -> (50000000 :: Int)+ False -> (5000000 :: Int)+ l :: Int+ l = case isSuffixOf "ByteString" s of+ True -> (539000002 :: Int)+ False -> (49000001 :: Int)+ (ex, out, err) <- LL.readCreateProcessWithExitCode (proc "time" ["bash", "-c", "yes | cat -n | head -n " <> show n]) i+ let t = parseTimeOutput err+ v = fromInteger (toEnum l) / t+ hPutStrLn stderr (printf "%7.2f million chars/second for " (v / 1000000.00) <> s <> " (" <> percentMessage v (expected s) <> ")")+ let r = v `about` expected s+ assertEqual "5M lines" (ExitSuccess, l, "Within 30% of usual speed") (ex, length out, r))+ -- This demonstrates lazy generation of process output. If+ -- you try this with a strict instance the call to+ -- readCreateProcessChunks will block indefinitely.+ , let expected "Lazy Text" = [Stderr "..",Stderr "..",Stderr "..",Stderr "..",Stderr "..",Stderr "..",Stderr "..",Stderr "..",Stderr ".."]+ expected "Lazy ByteString" = [Stderr "................................",Stderr ".................................",+ Stderr ".................................",Stderr ".................................",+ Stderr "................................",Stderr ".................................",+ Stderr ".................................",Stderr ".................................",+ Stderr "................................"]+ expected _ = error "unexpected" in+ testLazyInstances+ (\ s i -> TestLabel s $ TestCase $ do+ (ProcessHandle _ : chunks) <- LL.readCreateProcess (proc "bash" ["-c", "yes | cat -n"]) i >>= return . take 10 . dotifyChunks 1000 (head (i <> "."))+ assertEqual "ten chunks" (expected s) chunks)+ , testInstances+ (\ s i -> TestLabel s $ TestCase $ do+ hPutStrLn stderr $ "deadlock test for " ++ s+ result <- timeout 1000000 $ print =<< LL.readProcessWithExitCode "sleep" ["2h"] i+ assertEqual "takano-akio deadlock test" Nothing result)+ ]+ ])++expected :: String -> Double+expected "String" = 23000000.0+expected "Strict ByteString" = 760000000.0+expected "Lazy ByteString" = 760000000.0+expected "Strict Text" = 44000000.0+expected "Lazy Text" = 47000000.0+expected s = error $ "Unexpected instance: " <> show s++percentMessage v expected =+ case v / expected of+ r | r < 1.0 -> printf "%01.2f pct slower than usual" (100.0 - 100.0 * r)+ r -> printf "%01.2f pct faster than usual" (100.0 * r - 100.0)++about :: Double -> Double -> String+about v expected =+ case v / expected of+ r | r >= 0.7 && r <= 1.3 -> "Within 30% of usual speed"+ _ -> percentMessage v expected++parseTimeOutput :: forall a c. (ListLike a c, Enum c) => a -> Double+parseTimeOutput a =+ case s =~ ("^(.*)system ([0-9]*):([0-9]*).([0-9]*)elapsed(.*)$" :: String) :: (String, String, String, [String]) of+ (_, _, _, [_, m, s, h, _]) -> (read m :: Double) * 60.0 + (read s :: Double) + (read ("0." <> h) :: Double)+ where+ s = map (chr . fromEnum) $ toList a