process-listlike 0.10 → 1.0
raw patch · 17 files changed
+176/−1123 lines, 17 filesdep −HUnitdep −mtldep −process-listlikedep ~ListLikedep ~basedep ~bytestring
Dependencies removed: HUnit, mtl, process-listlike, regex-posix, unix, utf8-string
Dependency ranges changed: ListLike, base, bytestring, process
Files
- process-listlike.cabal +6/−23
- src/System/Process/ByteString.hs +18/−19
- src/System/Process/ByteString/Lazy.hs +21/−20
- src/System/Process/Chunks.hs +0/−230
- src/System/Process/Extras.hs +40/−0
- src/System/Process/ListLike.hs +55/−7
- src/System/Process/ListLike/Classes.hs +0/−38
- src/System/Process/ListLike/Instances.hs +0/−43
- src/System/Process/ListLike/LazyString.hs +0/−15
- src/System/Process/ListLike/Read.hs +0/−165
- src/System/Process/ListLike/ReadNoThreads.hs +0/−173
- src/System/Process/ListLike/StrictString.hs +0/−9
- src/System/Process/String.hs +0/−19
- src/System/Process/Text.hs +17/−19
- src/System/Process/Text/Lazy.hs +19/−17
- tests/Interactive.hs +0/−74
- tests/Main.hs +0/−252
process-listlike.cabal view
@@ -1,16 +1,16 @@ Name: process-listlike-Version: 0.10+Version: 1.0 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-Maintainer: David Lazar <lazar6@illinois.edu>+Author: David Lazar, Bas van Dijk, David Fox+Maintainer: David Fox <dsf@seereason.com> Category: System Build-type: Simple-Cabal-version: >= 1.8+Cabal-version: >=1.6 Extra-source-files: README.md @@ -26,15 +26,8 @@ Exposed-modules: System.Process.ByteString System.Process.ByteString.Lazy- System.Process.Chunks+ System.Process.Extras 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 @@ -43,18 +36,8 @@ Build-depends: base >= 4 && < 5,+ ListLike >= 4, process, bytestring,- ListLike >= 4,- mtl, text, deepseq--Executable process-listlike-tests- 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, bytestring, deepseq, HUnit, ListLike, mtl, process, process-listlike, text, utf8-string, unix
src/System/Process/ByteString.hs view
@@ -1,28 +1,27 @@-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} module System.Process.ByteString where +import Control.Exception+import Control.Monad import Data.ByteString (ByteString)+import Data.ListLike (null)+import Data.ListLike.IO (hGetContents, hPutStr)+import Data.Word (Word8)+import Prelude hiding (null) import System.Process-import System.Process.ListLike.Classes (ProcessOutput)-import qualified System.Process.ListLike.Read as LL (readCreateProcess, readProcess)+import qualified System.Process.ListLike as LL 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+import System.IO (hClose, hFlush)+import Utils (forkWait) -- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+instance LL.ListLikeProcessIO ByteString Word8 where+ forceOutput = return++-- | Specialized version for backwards compatibility. readProcessWithExitCode :: FilePath -> [String] -> ByteString -> IO (ExitCode, ByteString, ByteString)-readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input+readProcessWithExitCode = LL.readProcessWithExitCode --- | Like 'System.Process.readProcess', but using 'ByteString'-readProcess :: FilePath -> [String] -> ByteString -> IO ByteString-readProcess = LL.readProcess+readCreateProcessWithExitCode :: CreateProcess -> ByteString -> IO (ExitCode, ByteString, ByteString)+readCreateProcessWithExitCode = LL.readCreateProcessWithExitCode
src/System/Process/ByteString/Lazy.hs view
@@ -1,28 +1,29 @@-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} module System.Process.ByteString.Lazy where +import Control.DeepSeq (force)+import Control.Exception+import qualified Control.Exception as C (evaluate)+import Control.Monad import Data.ByteString.Lazy (ByteString)-import System.Exit (ExitCode)+import Data.ListLike (null)+import Data.ListLike.IO (hGetContents, hPutStr)+import Data.Word (Word8)+import Prelude hiding (null) 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+import qualified System.Process.ListLike as LL+import System.Exit (ExitCode)+import System.IO (hClose, hFlush)+import Utils (forkWait) -- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+instance LL.ListLikeProcessIO ByteString Word8 where+ forceOutput = C.evaluate . force++-- | Specialized version for backwards compatibility. readProcessWithExitCode :: FilePath -> [String] -> ByteString -> IO (ExitCode, ByteString, ByteString)-readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input+readProcessWithExitCode = LL.readProcessWithExitCode --- | Like 'System.Process.readProcess', but using 'ByteString'-readProcess :: FilePath -> [String] -> ByteString -> IO ByteString-readProcess = LL.readProcess+readCreateProcessWithExitCode :: CreateProcess -> ByteString -> IO (ExitCode, ByteString, ByteString)+readCreateProcessWithExitCode = LL.readCreateProcessWithExitCode
− src/System/Process/Chunks.hs
@@ -1,230 +0,0 @@--- | 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/Extras.hs view
@@ -0,0 +1,40 @@+-- | Re-export all symbols and instances of the process-extras package.+{-# LANGUAGE FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, UndecidableInstances #-}+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}+module System.Process.Extras+ ( ListLikeProcessIO(forceOutput)+ , readCreateProcessWithExitCode+ , readProcessWithExitCode+ , showCreateProcessForUser+ , showCmdSpecForUser+ ) where++import Control.DeepSeq (force)+import qualified Control.Exception as C (evaluate)+-- import Data.Text (pack, unpack)+import System.Process (CmdSpec(..), CreateProcess(..), showCommandForUser)+import System.Process.ByteString ()+import System.Process.ByteString.Lazy ()+import System.Process.ListLike (ListLikeProcessIO(forceOutput), readCreateProcessWithExitCode, readProcessWithExitCode)+import System.Process.Text ()+import System.Process.Text.Lazy ()++-- | System.Process utility functions.+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++-- | Like 'System.Process.readProcessWithExitCode' that takes a 'CreateProcess'.+instance ListLikeProcessIO String Char where+ -- This is required because strings are magically lazy. Without it+ -- processes get exit status 13 - file read failures.+ forceOutput = C.evaluate . force+{-+ readCreateProcessWithExitCode p input = do+ (code, out, err) <- readCreateProcessWithExitCode p (pack input)+ return (code, unpack out, unpack err)+-}
src/System/Process/ListLike.hs view
@@ -1,8 +1,56 @@-module System.Process.ListLike- ( module System.Process.ListLike.Classes- , module System.Process.ListLike.Read- ) where+{-# LANGUAGE MultiParamTypeClasses #-}+module System.Process.ListLike where -import System.Process.ListLike.Classes-import System.Process.ListLike.Instances ()-import System.Process.ListLike.Read+import Control.Exception+import Control.Monad+import Data.ListLike (null)+import Data.ListLike.IO (ListLikeIO, hGetContents, hPutStr)+import Prelude hiding (null)+import System.Exit (ExitCode)+import System.IO (hClose, hFlush)+import System.Process+import Utils (forkWait)++class ListLikeIO a c => ListLikeProcessIO a c where+ forceOutput :: a -> IO a++-- | Like 'System.Process.readProcessWithExitCode', but with generalized input and output type.+readProcessWithExitCode+ :: ListLikeProcessIO 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++readCreateProcessWithExitCode :: ListLikeProcessIO a c => CreateProcess -> a -> IO (ExitCode, a, a)+readCreateProcessWithExitCode p input = mask $ \restore -> do+ (Just inh, Just outh, Just errh, pid) <-+ createProcess p{ std_in = CreatePipe,+ std_out = CreatePipe,+ std_err = CreatePipe }+ flip onException+ (do terminateProcess pid; hClose inh; hClose outh; hClose errh;+ waitForProcess pid) $ restore $ do++ -- fork off a thread to start consuming stdout+ waitOut <- forkWait $ hGetContents outh >>= forceOutput++ -- fork off a thread to start consuming stderr+ waitErr <- forkWait $ hGetContents errh >>= forceOutput++ -- now write and flush any input+ unless (null input) $ do hPutStr inh input; hFlush inh+ hClose inh -- done with stdin++ -- wait on the output+ out <- waitOut+ err <- waitErr++ hClose outh+ hClose errh++ -- wait on the process+ ex <- waitForProcess pid++ return (ex, out, err)
− src/System/Process/ListLike/Classes.hs
@@ -1,38 +0,0 @@-{-# 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
@@ -1,43 +0,0 @@-{-# 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
@@ -1,15 +0,0 @@-{-# 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
@@ -1,165 +0,0 @@--- | 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
@@ -1,173 +0,0 @@-{-# 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
@@ -1,9 +0,0 @@-{-# 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
@@ -1,19 +0,0 @@-{-# 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
@@ -1,28 +1,26 @@-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} module System.Process.Text where +import Control.Exception+import Control.Monad import Data.Text (Text)+import Data.ListLike (null)+import Data.ListLike.IO (hGetContents, hPutStr)+import Prelude hiding (null) import System.Process-import System.Process.ListLike.Classes (ProcessOutput)-import qualified System.Process.ListLike.Read as LL (readCreateProcess, readProcess)+import qualified System.Process.ListLike as LL import System.Exit (ExitCode)--readCreateProcess :: ProcessOutput Text b => CreateProcess -> Text -> IO b-readCreateProcess = LL.readCreateProcess+import System.IO (hClose, hFlush)+import Utils (forkWait) --- | 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 'Text'+instance LL.ListLikeProcessIO Text Char where+ forceOutput = return --- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+-- | Specialized version for backwards compatibility. readProcessWithExitCode :: FilePath -> [String] -> Text -> IO (ExitCode, Text, Text)-readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input+readProcessWithExitCode = LL.readProcessWithExitCode --- | Like 'System.Process.readProcess', but using 'Text'-readProcess :: FilePath -> [String] -> Text -> IO Text-readProcess = LL.readProcess+readCreateProcessWithExitCode :: CreateProcess -> Text -> IO (ExitCode, Text, Text)+readCreateProcessWithExitCode = LL.readCreateProcessWithExitCode
src/System/Process/Text/Lazy.hs view
@@ -1,26 +1,28 @@-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} module System.Process.Text.Lazy where +import Control.DeepSeq (force)+import Control.Exception+import qualified Control.Exception as C (evaluate)+import Control.Monad+import Data.ListLike (null)+import Data.ListLike.IO (hGetContents, hPutStr) import Data.Text.Lazy (Text)-import System.Exit (ExitCode)+import Prelude hiding (null) 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+import qualified System.Process.ListLike as LL+import System.Exit (ExitCode)+import System.IO (hClose, hFlush)+import Utils (forkWait) -- | 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+instance LL.ListLikeProcessIO Text Char where+ forceOutput = C.evaluate . force --- | Like 'System.Process.readProcessWithExitCode', but using 'ByteString'+-- | Specialized version for backwards compatibility. readProcessWithExitCode :: FilePath -> [String] -> Text -> IO (ExitCode, Text, Text)-readProcessWithExitCode cmd args input = readCreateProcessWithExitCode (proc cmd args) input+readProcessWithExitCode = LL.readProcessWithExitCode --- | Like 'System.Process.readProcess', but using 'Text'-readProcess :: FilePath -> [String] -> Text -> IO Text-readProcess = LL.readProcess+readCreateProcessWithExitCode :: CreateProcess -> Text -> IO (ExitCode, Text, Text)+readCreateProcessWithExitCode = LL.readCreateProcessWithExitCode
− tests/Interactive.hs
@@ -1,74 +0,0 @@-{-# 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
@@ -1,252 +0,0 @@-{-# 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