pipes-cliff 0.4.0.0 → 0.6.0.0
raw patch · 4 files changed
+844/−633 lines, 4 filesdep ~pipes-concurrencyPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: pipes-concurrency
API changes (from Hackage documentation)
- Pipes.Cliff: quiet :: CreateProcess -> Bool
+ Pipes.Cliff: Closing :: Activity
+ Pipes.Cliff: Error :: HandleDesc
+ Pipes.Cliff: HandleOopsie :: Activity -> HandleDesc -> HandleOopsie
+ Pipes.Cliff: Input :: HandleDesc
+ Pipes.Cliff: Oopsie :: (Maybe HandleOopsie) -> CmdSpec -> IOException -> Oopsie
+ Pipes.Cliff: Output :: HandleDesc
+ Pipes.Cliff: RawCommand :: FilePath -> [String] -> CmdSpec
+ Pipes.Cliff: Reading :: Activity
+ Pipes.Cliff: ShellCommand :: String -> CmdSpec
+ Pipes.Cliff: Writing :: Activity
+ Pipes.Cliff: data Activity
+ Pipes.Cliff: data CmdSpec
+ Pipes.Cliff: data HandleDesc
+ Pipes.Cliff: data HandleOopsie
+ Pipes.Cliff: data Oopsie
+ Pipes.Cliff: handler :: CreateProcess -> Oopsie -> IO ()
+ Pipes.Cliff: squelch :: Monad m => a -> m ()
+ Pipes.Cliff.Core: Closing :: Activity
+ Pipes.Cliff.Core: CreateProcess :: CmdSpec -> Maybe FilePath -> Maybe [(String, String)] -> Bool -> Bool -> Bool -> (Oopsie -> IO ()) -> CreateProcess
+ Pipes.Cliff.Core: ErrSpec :: (Oopsie -> IO ()) -> CmdSpec -> ErrSpec
+ Pipes.Cliff.Core: Error :: HandleDesc
+ Pipes.Cliff.Core: HandleOopsie :: Activity -> HandleDesc -> HandleOopsie
+ Pipes.Cliff.Core: Inherit :: NonPipe
+ Pipes.Cliff.Core: Input :: HandleDesc
+ Pipes.Cliff.Core: Oopsie :: (Maybe HandleOopsie) -> CmdSpec -> IOException -> Oopsie
+ Pipes.Cliff.Core: Output :: HandleDesc
+ Pipes.Cliff.Core: RawCommand :: FilePath -> [String] -> CmdSpec
+ Pipes.Cliff.Core: Reading :: Activity
+ Pipes.Cliff.Core: ShellCommand :: String -> CmdSpec
+ Pipes.Cliff.Core: UseHandle :: Handle -> NonPipe
+ Pipes.Cliff.Core: Writing :: Activity
+ Pipes.Cliff.Core: acquire :: MonadSafe m => Base m a -> (a -> Base m ()) -> m a
+ Pipes.Cliff.Core: background :: MonadSafe m => IO a -> m (Async a)
+ Pipes.Cliff.Core: backgroundReceiveFromProcess :: MonadSafe m => HandleDesc -> Handle -> Consumer ByteString (SafeT IO) () -> ErrSpec -> m ()
+ Pipes.Cliff.Core: backgroundSendToProcess :: MonadSafe m => Handle -> Producer ByteString (SafeT IO) () -> ErrSpec -> m ()
+ Pipes.Cliff.Core: bufSize :: Int
+ Pipes.Cliff.Core: closeHandleNoThrow :: (MonadCatch m, MonadIO m) => Handle -> HandleDesc -> ErrSpec -> m ()
+ Pipes.Cliff.Core: close_fds :: CreateProcess -> Bool
+ Pipes.Cliff.Core: cmdspec :: CreateProcess -> CmdSpec
+ Pipes.Cliff.Core: consumeToHandle :: (MonadSafe m, MonadCatch (Base m)) => Handle -> ErrSpec -> Consumer ByteString m ()
+ Pipes.Cliff.Core: convertCmdSpec :: CmdSpec -> CmdSpec
+ Pipes.Cliff.Core: convertCreateProcess :: Maybe NonPipe -> Maybe NonPipe -> Maybe NonPipe -> CreateProcess -> CreateProcess
+ Pipes.Cliff.Core: convertNonPipe :: Maybe NonPipe -> StdStream
+ Pipes.Cliff.Core: conveyor :: MonadSafe m => Effect (SafeT IO) () -> m ()
+ Pipes.Cliff.Core: createProcess :: (MonadSafe m, MonadCatch (Base m)) => CreateProcess -> ErrSpec -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
+ Pipes.Cliff.Core: create_group :: CreateProcess -> Bool
+ Pipes.Cliff.Core: cwd :: CreateProcess -> Maybe FilePath
+ Pipes.Cliff.Core: data Activity
+ Pipes.Cliff.Core: data CmdSpec
+ Pipes.Cliff.Core: data CreateProcess
+ Pipes.Cliff.Core: data ErrSpec
+ Pipes.Cliff.Core: data HandleDesc
+ Pipes.Cliff.Core: data HandleOopsie
+ Pipes.Cliff.Core: data NonPipe
+ Pipes.Cliff.Core: data Oopsie
+ Pipes.Cliff.Core: defaultHandler :: Oopsie -> IO ()
+ Pipes.Cliff.Core: delegate_ctlc :: CreateProcess -> Bool
+ Pipes.Cliff.Core: env :: CreateProcess -> Maybe [(String, String)]
+ Pipes.Cliff.Core: esCmdSpec :: ErrSpec -> CmdSpec
+ Pipes.Cliff.Core: esErrorHandler :: ErrSpec -> Oopsie -> IO ()
+ Pipes.Cliff.Core: handleErrors :: (MonadCatch m, MonadIO m) => Maybe HandleOopsie -> ErrSpec -> m () -> m ()
+ Pipes.Cliff.Core: handleException :: MonadIO m => Maybe HandleOopsie -> IOException -> ErrSpec -> m ()
+ Pipes.Cliff.Core: handler :: CreateProcess -> Oopsie -> IO ()
+ Pipes.Cliff.Core: instance Eq Activity
+ Pipes.Cliff.Core: instance Eq CmdSpec
+ Pipes.Cliff.Core: instance Eq HandleDesc
+ Pipes.Cliff.Core: instance Eq HandleOopsie
+ Pipes.Cliff.Core: instance Eq Oopsie
+ Pipes.Cliff.Core: instance Ord Activity
+ Pipes.Cliff.Core: instance Ord CmdSpec
+ Pipes.Cliff.Core: instance Ord HandleDesc
+ Pipes.Cliff.Core: instance Show Activity
+ Pipes.Cliff.Core: instance Show CmdSpec
+ Pipes.Cliff.Core: instance Show HandleDesc
+ Pipes.Cliff.Core: instance Show HandleOopsie
+ Pipes.Cliff.Core: instance Show Oopsie
+ Pipes.Cliff.Core: makeErrSpec :: CreateProcess -> ErrSpec
+ Pipes.Cliff.Core: messageBuffer :: Buffer a
+ Pipes.Cliff.Core: newMailbox :: (MonadIO m, MonadSafe mi, MonadSafe mo) => m (Consumer a mi (), Producer a mo ())
+ Pipes.Cliff.Core: pipeError :: (MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> CreateProcess -> m (Producer ByteString m (), ProcessHandle)
+ Pipes.Cliff.Core: pipeInput :: (MonadSafe mi, MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> CreateProcess -> m (Consumer ByteString mi (), ProcessHandle)
+ Pipes.Cliff.Core: pipeInputError :: (MonadSafe mi, MonadSafe me, MonadSafe m, MonadCatch (Base m)) => NonPipe -> CreateProcess -> m ((Consumer ByteString mi (), Producer ByteString me ()), ProcessHandle)
+ Pipes.Cliff.Core: pipeInputOutput :: (MonadSafe mi, MonadSafe mo, MonadSafe m, MonadCatch (Base m)) => NonPipe -> CreateProcess -> m ((Consumer ByteString mi (), Producer ByteString mo ()), ProcessHandle)
+ Pipes.Cliff.Core: pipeInputOutputError :: (MonadSafe mi, MonadSafe mo, MonadSafe me, MonadSafe m, MonadCatch (Base m)) => CreateProcess -> m ((Consumer ByteString mi (), Producer ByteString mo (), Producer ByteString me ()), ProcessHandle)
+ Pipes.Cliff.Core: pipeNone :: (MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> NonPipe -> CreateProcess -> m ProcessHandle
+ Pipes.Cliff.Core: pipeOutput :: (MonadSafe mo, MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> CreateProcess -> m (Producer ByteString mo (), ProcessHandle)
+ Pipes.Cliff.Core: pipeOutputError :: (MonadSafe mo, MonadSafe me, MonadSafe m, MonadCatch (Base m)) => NonPipe -> CreateProcess -> m ((Producer ByteString mo (), Producer ByteString me ()), ProcessHandle)
+ Pipes.Cliff.Core: procSpec :: String -> [String] -> CreateProcess
+ Pipes.Cliff.Core: produceFromHandle :: (MonadSafe m, MonadCatch (Base m)) => HandleDesc -> Handle -> ErrSpec -> Producer ByteString m ()
+ Pipes.Cliff.Core: renderOopsie :: String -> Oopsie -> String
+ Pipes.Cliff.Core: runCreateProcess :: (MonadSafe m, MonadCatch (Base m)) => Maybe NonPipe -> Maybe NonPipe -> Maybe NonPipe -> CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ErrSpec, ProcessHandle)
+ Pipes.Cliff.Core: runInputHandle :: (MonadSafe m, MonadSafe mi) => Handle -> ErrSpec -> m (Consumer ByteString mi ())
+ Pipes.Cliff.Core: runOutputHandle :: (MonadSafe m, MonadSafe mo) => HandleDesc -> Handle -> ErrSpec -> m (Producer ByteString mo ())
+ Pipes.Cliff.Core: squelch :: Monad m => a -> m ()
+ Pipes.Cliff.Core: terminateProcess :: (MonadCatch m, MonadIO m) => ProcessHandle -> ErrSpec -> m ()
+ Pipes.Cliff.Core: waitForProcess :: MonadIO m => ProcessHandle -> m ExitCode
+ Pipes.Cliff.Core: waitForThread :: MonadIO m => Async a -> m a
- Pipes.Cliff: CreateProcess :: CmdSpec -> Maybe FilePath -> Maybe [(String, String)] -> Bool -> Bool -> Bool -> Bool -> CreateProcess
+ Pipes.Cliff: CreateProcess :: CmdSpec -> Maybe FilePath -> Maybe [(String, String)] -> Bool -> Bool -> Bool -> (Oopsie -> IO ()) -> CreateProcess
- Pipes.Cliff: conveyor :: Effect (SafeT IO) () -> SafeT IO ()
+ Pipes.Cliff: conveyor :: MonadSafe m => Effect (SafeT IO) () -> m ()
- Pipes.Cliff: pipeError :: (MonadSafe mi, MonadSafe m) => NonPipe -> NonPipe -> CreateProcess -> m (Producer ByteString mi (), ProcessHandle)
+ Pipes.Cliff: pipeError :: (MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> CreateProcess -> m (Producer ByteString m (), ProcessHandle)
- Pipes.Cliff: pipeInput :: (MonadSafe mi, MonadSafe m) => NonPipe -> NonPipe -> CreateProcess -> m (Consumer ByteString mi (), ProcessHandle)
+ Pipes.Cliff: pipeInput :: (MonadSafe mi, MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> CreateProcess -> m (Consumer ByteString mi (), ProcessHandle)
- Pipes.Cliff: pipeInputError :: (MonadSafe mi, MonadSafe mo, MonadSafe m) => NonPipe -> CreateProcess -> m (Consumer ByteString mi (), Producer ByteString mo (), ProcessHandle)
+ Pipes.Cliff: pipeInputError :: (MonadSafe mi, MonadSafe me, MonadSafe m, MonadCatch (Base m)) => NonPipe -> CreateProcess -> m ((Consumer ByteString mi (), Producer ByteString me ()), ProcessHandle)
- Pipes.Cliff: pipeInputOutput :: (MonadSafe mi, MonadSafe mo, MonadSafe m) => NonPipe -> CreateProcess -> m (Consumer ByteString mi (), Producer ByteString mo (), ProcessHandle)
+ Pipes.Cliff: pipeInputOutput :: (MonadSafe mi, MonadSafe mo, MonadSafe m, MonadCatch (Base m)) => NonPipe -> CreateProcess -> m ((Consumer ByteString mi (), Producer ByteString mo ()), ProcessHandle)
- Pipes.Cliff: pipeInputOutputError :: (MonadSafe mi, MonadSafe mo, MonadSafe me, MonadSafe m) => CreateProcess -> m (Consumer ByteString mi (), Producer ByteString mo (), Producer ByteString me (), ProcessHandle)
+ Pipes.Cliff: pipeInputOutputError :: (MonadSafe mi, MonadSafe mo, MonadSafe me, MonadSafe m, MonadCatch (Base m)) => CreateProcess -> m ((Consumer ByteString mi (), Producer ByteString mo (), Producer ByteString me ()), ProcessHandle)
- Pipes.Cliff: pipeNone :: MonadSafe m => NonPipe -> NonPipe -> NonPipe -> CreateProcess -> m ProcessHandle
+ Pipes.Cliff: pipeNone :: (MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> NonPipe -> CreateProcess -> m ProcessHandle
- Pipes.Cliff: pipeOutput :: (MonadSafe mi, MonadSafe m) => NonPipe -> NonPipe -> CreateProcess -> m (Producer ByteString mi (), ProcessHandle)
+ Pipes.Cliff: pipeOutput :: (MonadSafe mo, MonadSafe m, MonadCatch (Base m)) => NonPipe -> NonPipe -> CreateProcess -> m (Producer ByteString mo (), ProcessHandle)
- Pipes.Cliff: pipeOutputError :: (MonadSafe mi, MonadSafe mo, MonadSafe m) => NonPipe -> CreateProcess -> m (Producer ByteString mi (), Producer ByteString mo (), ProcessHandle)
+ Pipes.Cliff: pipeOutputError :: (MonadSafe mo, MonadSafe me, MonadSafe m, MonadCatch (Base m)) => NonPipe -> CreateProcess -> m ((Producer ByteString mo (), Producer ByteString me ()), ProcessHandle)
Files
- lib/Pipes/Cliff.hs +75/−579
- lib/Pipes/Cliff/Core.hs +709/−0
- lib/Pipes/Cliff/Examples.hs +46/−46
- pipes-cliff.cabal +14/−8
lib/Pipes/Cliff.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE RankNTypes #-} -- | Spawn subprocesses and interact with them using "Pipes" -- -- The interface in this module deliberately resembles the interface@@ -12,7 +11,7 @@ -- 'pipeInput' or 'pipeInputOutput' to specify what streams you want -- to use a 'Proxy' for and what streams you wish to be 'Inherit'ed -- or if you want to 'UseHandle'. You then send or receive--- information using the returned 'Proxy'.+-- information using one or more 'Proxy'. -- -- __Use the @-threaded@ GHC option__ when compiling your programs or -- when using GHCi. Internally, this module uses@@ -48,12 +47,16 @@ -- There you will find references to other libraries that you might -- find more useful than this one. ----- For some simple examples, consult "Pipes.Cliff.Examples".+-- You will want to consult "Pipes.Cliff.Examples" for some examples+-- before getting started. There are some important notes in there+-- about how to run pipelines. module Pipes.Cliff ( -- * Specifying a subprocess's properties- NonPipe(..)+ CmdSpec(..)+ , NonPipe(..) , CreateProcess(..) , procSpec+ , squelch -- * Creating processes -- $process@@ -78,418 +81,33 @@ , waitForProcess , waitForThread + -- * Errors and warnings++ -- | You will only need what's in this section if you want to+ -- examine errors more closely.+ , Activity(..)+ , HandleDesc(..)+ , HandleOopsie(..)+ , Oopsie(..)+ -- * Re-exports -- $reexports+ , module Control.Concurrent.MVar , module Pipes , module Pipes.Safe , module System.Exit , module System.Process++ -- * Some design notes+ -- $designNotes ) where -import System.IO+import Pipes.Cliff.Core import Pipes-import Pipes.Concurrent-import Control.Concurrent.Async (Async, async, cancel, wait)-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import qualified System.Process as Process-import System.Process (ProcessHandle)-import qualified Control.Exception-import Pipes.Safe (runSafeT)-import qualified Pipes.Safe+import Pipes.Safe import System.Exit-import System.Environment (getProgName)-import qualified System.IO as IO-import Control.Monad (when, liftM)---- # Exception handling---- | Runs a particular action. Any IO errors are caught; a warning--- message is printed if we're not being quiet, and 'Nothing' is--- returned. Otherwise, the result is returned.--catchAndWarn- :: (Pipes.Safe.MonadCatch m, MonadIO m)- => Bool- -- ^ Be quiet?- -> String- -- ^ What are we doing? Used for warning message.- -> m a- -- ^ Do this- -> m (Maybe a)-catchAndWarn beQuiet desc act- = Pipes.Safe.catch (liftM Just act) handle- where- handle e = do- let _types = e :: Control.Exception.IOException- when (not beQuiet) . liftIO $ do- pn <- getProgName- let msg = pn ++ ": warning: caught exception when "- ++ desc ++ ": " ++ show e- IO.hPutStrLn IO.stderr msg- return Nothing---- | Runs a particular action, ignoring all IO errors. Sometimes--- using hClose will result in a broken pipe error. Since the process--- may have already been shut down, this is to be expected. Since--- there is nothing that can really be done to respond to any IO error--- that results from closing a handle, just ignore these errors.-ignoreIOExceptions :: Bool -> String -> IO () -> IO ()-ignoreIOExceptions beQuiet desc a = Control.Exception.catch a f- where- f :: Control.Exception.IOException -> IO ()- f exc = when (not beQuiet) $ do- pn <- getProgName- let msg = pn ++ ": warning: ignoring exception caught when " ++ desc- ++ ": " ++ show exc- IO.hPutStrLn IO.stderr msg- return ()---- | Close a handle, ignoring all IO exceptions.-closeHandle- :: Bool- -- ^ Be quiet?- -> Handle- -> IO ()-closeHandle q h = ignoreIOExceptions q "closing handle" (hClose h)---- | Terminate a process, ignoring all IO exceptions.-terminateProcess- :: Bool- -- ^ Be quiet?- -> Process.ProcessHandle- -> IO ()-terminateProcess q h = ignoreIOExceptions q "terminating process" $ do- _ <- Process.terminateProcess h- _ <- Process.waitForProcess h- return ()----- # Configuration and result types---- | How will the subprocess get its information for this stream?-data NonPipe- = Inherit- -- ^ Use whatever stream that the parent process has.- | UseHandle Handle- -- ^ Use the given handle for input or output---- | Like 'System.Process.CreateProcess' in "System.Process",--- this gives the necessary information to create a subprocess. All--- but one of these fields is also present in--- 'System.Process.CreateProcess', and they all have the same meaning;--- the only field that is different is the 'quiet' field.-data CreateProcess = CreateProcess- { cmdspec :: Process.CmdSpec- -- ^ Executable and arguments, or shell command- , cwd :: Maybe FilePath- -- ^ A new current working directory for the subprocess; if- -- 'Nothing', use the calling process's working directory.- , env :: Maybe [(String, String)]- -- ^ The environment for the subprocess; if 'Nothing', use the- -- calling process's working directory.-- , close_fds :: Bool- -- ^ If 'True', close all file descriptors other than the standard- -- descriptors. See the documentation for- -- 'System.Process.close_fds' for details on how this works in- -- Windows.- , create_group :: Bool- -- ^ If 'True', create a new process group.- , delegate_ctlc :: Bool- -- ^ See 'System.Process.delegate_ctlc' in the "System.Process"- -- module for details.-- , quiet :: Bool- -- ^ If True, does not print messages to standard error when IO- -- exceptions arise when closing handles or terminating processes.- -- Sometimes these errors arise due to broken pipes; this can be- -- normal, depending on the circumstances. For example, if you- -- are streaming a large set of values to a pager such as @less@- -- and you expect that the user will often quit the pager without- -- viewing the whole result, a broken pipe will result, which will- -- print a warning message. That can be a nuisance. If you don't- -- want to see these errors, set 'quiet' to 'True'.- }--convertCreateProcess- :: Maybe NonPipe- -> Maybe NonPipe- -> Maybe NonPipe- -> CreateProcess- -> Process.CreateProcess-convertCreateProcess inp out err a = Process.CreateProcess- { Process.cmdspec = cmdspec a- , Process.cwd = cwd a- , Process.env = env a- , Process.std_in = conv inp- , Process.std_out = conv out- , Process.std_err = conv err- , Process.close_fds = close_fds a- , Process.create_group = create_group a- , Process.delegate_ctlc = delegate_ctlc a- }- where- conv = convertNonPipe--convertNonPipe :: Maybe NonPipe -> Process.StdStream-convertNonPipe a = case a of- Nothing -> Process.CreatePipe- Just Inherit -> Process.Inherit- Just (UseHandle h) -> Process.UseHandle h---- | Create a 'CreateProcess' record with default settings. The--- default settings are:------ * a raw command (as opposed to a shell command) is created------ * the current working directory is not changed from the parent process------ * the environment is not changed from the parent process------ * the parent's file descriptors (other than standard input,--- standard output, and standard error) are inherited------ * no new process group is created------ * 'delegate_ctlc' is 'False'------ * 'quiet' is 'False'--procSpec- :: String- -- ^ The name of the program to run, such as @less@.- -> [String]- -- ^ Command-line arguments- -> CreateProcess-procSpec prog args = CreateProcess- { cmdspec = Process.RawCommand prog args- , cwd = Nothing- , env = Nothing- , close_fds = False- , create_group = False- , delegate_ctlc = False- , quiet = False- }----- # Pipes---- | I have no idea what this should be. I'll start with a simple--- small value and see how it works.-bufSize :: Int-bufSize = 1024---- | Create a 'Producer' from a 'Handle'. The 'Producer' will get--- 'ByteString' from the 'Handle' and produce them. Does nothing to--- close the given 'Handle' at any time. If there is an IO error--- while receiving data, the error is caught and production ceases.--- The exception is not re-thrown.-produceFromHandle- :: (Pipes.Safe.MonadCatch m, MonadIO m)- => Bool- -- ^ Be quiet?- -> Handle- -> Producer ByteString m ()-produceFromHandle beQuiet h = catchAndWarn beQuiet desc act >>= go- where- desc = "receiving data from process"- act = liftIO (BS.hGetSome h bufSize)- go Nothing = return ()- go (Just bs)- | BS.null bs = return ()- | otherwise = yield bs >> produceFromHandle beQuiet h----- | Create a 'Consumer' from a 'Handle'. The 'Consumer' will put--- each 'ByteString' it receives into the 'Handle'. Does nothing to--- close the handle at any time. If there is an IO error while--- sending data, the error is caught and consumption ceases. The--- exception is not re-thrown.-consumeToHandle- :: (Pipes.Safe.MonadCatch m, MonadIO m)- => Bool- -- ^ Be quiet?- -> Handle- -> Consumer ByteString m ()-consumeToHandle beQuiet h = do- bs <- await- mayRes <- catchAndWarn beQuiet "sending data to process"- (liftIO $ BS.hPut h bs)- case mayRes of- Nothing -> return ()- Just _ -> consumeToHandle beQuiet h----- | A buffer that holds 10 messages. I have no idea if this is the--- ideal size. Don't use an unbounded buffer, though, because with--- unbounded producers an unbounded buffer will fill up your RAM.-messageBuffer :: Buffer a-messageBuffer = bounded 10---- | Acquires a resource and registers a finalizer.-initialize- :: Pipes.Safe.MonadSafe m- => IO a- -> (a -> IO ())- -> m a-initialize make destroy = Pipes.Safe.mask $ \_ -> do- thing <- liftIO make- _ <- Pipes.Safe.register (liftIO (destroy thing))- return thing---- | Creates a mailbox; seals it when done.-newMailbox- :: Pipes.Safe.MonadSafe m- => m (Output a, Input a, STM ())-newMailbox =- initialize (spawn' messageBuffer)- (\(_, _, seal) -> atomically seal)---- | Runs a thread in the background. Initializes a finalizer that--- will cancel the thread if it is still running when the--- 'Pipes.Safe.MonadSafe' computation completes.-background :: Pipes.Safe.MonadSafe m => IO a -> m (Async a)-background action = initialize (async action) cancel---- | Creates a thread that will run in the background and pump--- messages from the given mailbox to the process via its handle.--- Closes the Handle when done.-processPump- :: Pipes.Safe.MonadSafe m- => Bool- -- ^ Quiet?- -> Handle- -> (Input ByteString, STM ())- -> m ()-processPump beQuiet hndle (input, seal) = do- let pumper = flip Control.Exception.finally cleanup .- runEffect $- fromInput input >-> consumeToHandle beQuiet hndle- _ <- background pumper- return ()- where- cleanup = liftIO $ do- closeHandle beQuiet hndle- atomically seal---- | Creates a thread that will run in the background and pull--- messages from the process and place them into the given mailbox.--- Closes the handle when done.-processPull- :: Pipes.Safe.MonadSafe m- => Bool- -- ^ Quiet?- -> Handle- -> (Output ByteString, STM ())- -- ^ Output box, paired with action to close the box.- -> m ()-processPull beQuiet hndle (output, seal) = do- let puller = flip Control.Exception.finally cleanup .- runEffect $- produceFromHandle beQuiet hndle >-> toOutput output- _ <- background puller- return ()- where- cleanup = liftIO $ do- closeHandle beQuiet hndle- atomically seal----- | Creates a mailbox that sends messages to the given process, and--- sets up and runs threads to pump messages to the process.-makeToProcess- :: (Pipes.Safe.MonadSafe mp, Pipes.Safe.MonadSafe m)- => Bool- -- ^ Quiet?- -> Handle- -> m (Consumer ByteString mp ())-makeToProcess beQuiet hndle = do- (out, inp, seal) <- newMailbox- processPump beQuiet hndle (inp, seal)- return $ toOutput out `Pipes.Safe.finally` (liftIO (atomically seal))---- | Creates a mailbox that receives messages from the given process,--- and sets up and runs threads to receive the messages and deliver--- them to the mailbox.-makeFromProcess- :: (Pipes.Safe.MonadSafe m, Pipes.Safe.MonadSafe mp)- => Bool- -- ^ Quiet?- -> Handle- -> m (Producer ByteString mp ())-makeFromProcess beQuiet hndle = do- (out, inp, seal) <- newMailbox- processPull beQuiet hndle (out, seal)- return $ fromInput inp `Pipes.Safe.finally` (liftIO (atomically seal))----- # Subprocesses---- Waiting on a process handle more than once will merely return the--- same code more than once. See the source code for System.Process.---- | Launch and use a subprocess.------ /Warning/ - do not attempt to use any of the resources created by--- the process after leaving the 'ContT' computation. They are all--- destroyed. So any Pipes you create using the 'Output' or 'Input'--- that connect to the process must finish their IO before you leave--- the 'ContT' computation. It's okay to return the--- 'System.Exit.ExitCode' that you get from running the process, or--- any data you get from the process--you just can't return something--- that must perform IO to interact with the process.------ Also, exiting the 'ContT' computation immediately destroys all--- subprocesses. If you want to make sure the process terminates--- first, use 'Process.waitForProcess' on the handle which you can get--- from 'mbxHandle' before leaving the 'ContT' computation.------ The upside of this warning is that because all subprocess resources--- are destroyed after leaving the 'ContT' computation, this function--- is exception safe.------ To increase the safety when using values with the 'ContT' type,--- you can use 'safeCliff'.----- | Creates a subprocess. Registers destroyers for each handle--- created, as well as for the ProcessHandle.-createProcess- :: Pipes.Safe.MonadSafe m- => Bool- -- ^ Quiet?- -> Process.CreateProcess- -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)-createProcess beQuiet cp = initialize (Process.createProcess cp) destroy- where- destroy (mayIn, mayOut, mayErr, han) = do- let close = maybe (return ()) (closeHandle beQuiet)- close mayIn- close mayOut- close mayErr- terminateProcess beQuiet han----- | Runs in the background an effect, typically one that is moving--- data from one process to another. For examples of its usage, see--- "Pipes.Cliff.Examples". The associated thread is killed when the--- 'SafeT' computation completes.-conveyor :: Effect (Pipes.Safe.SafeT IO) () -> Pipes.Safe.SafeT IO ()-conveyor efct- = (background . liftIO . runSafeT . runEffect $ efct) >> return ()---- | A version of 'System.Process.waitForProcess' with an overloaded--- 'MonadIO' return type.-waitForProcess :: MonadIO m => ProcessHandle -> m ExitCode-waitForProcess h = liftIO $ Process.waitForProcess h---- | A version of 'Control.Concurrent.Async.wait' with an overloaded--- 'MonadIO' return type. Allows you to wait for the return value of--- threads launched with 'background'. If the thread throws an--- exception, 'waitForThread' will throw that same exception.-waitForThread :: MonadIO m => Async a -> m a-waitForThread = liftIO . wait+import System.Process (ProcessHandle)+import Control.Concurrent.MVar {- $process @@ -502,198 +120,76 @@ subprocess that does not create a Pipe for any of the standard streams, use 'pipeNone'. You must describe what you want done with standard input, standard output, and standard error. To create a-subprocess that creates a Pipe for standard input and standard+subprocess that creates a 'Proxy' for standard input and standard output, use 'pipeInputOutput'. You must describe what you want done with standard error. A 'Producer' is returned for standard output and a 'Consumer' for standard input. -Do NOT attempt to use any of the resources created by this function-outside of the 'Pipes.Safe.MonadSafe' computation. The-'Pipes.Safe.MonadSafe' computation will destroy all resources when-the 'Pipes.Safe.MonadSafe' computation is complete. This means that-all these functions are exception safe: all resources--threads and-processes included--are destroyed when the 'Pipes.Safe.MonadSafe'-computation is complete, even if an exception is thrown. However,-this does mean that you need to stay in the 'Pipes.Safe.MonadSafe'-computation if you need to keep a resource around. 'waitForProcess'-can be handy for this. All functions in this section return a-'ProcessHandle' for use with 'waitForProcess'.+If you are creating a 'Proxy' for only one stream (for instance,+you're using 'pipeOutput') then a single 'Proxy' is returned to you.+That 'Proxy' manages all the resources it creates; so, for example,+when you ultimately run your 'Effect', the process is created and then+destroyed when the 'MonadSafe' computation completes. -Each 'Proxy' automatically destroys associated file handles and other-behind-the-scenes resources after its computation finishes running;-that's why the monad stack of each 'Proxy' must contain a-'Pipes.Safe.MonadSafe'. For an example of how to deal with the-'Pipes.Safe.MonadSafe' class, consult "Pipes.Cliff.Examples".+If you are creating a 'Proxy' for more than one stream (for+instance, you're using 'pipeInputOutput') then the multiple 'Proxy'+are returned to you in a tuple in the 'MonadSafe' computation. The+'MonadSafe' computation will make sure that the resulting process+and handles are destroyed when you exit the 'MonadSafe' computation.+In such a case, you must make sure that you don't try to use the+streams outside of the 'MonadSafe' computation, because the+subprocess will already be destroyed. To make sure you are done+using the streams before leaving the 'MonadSafe' computation, you+will want to use 'waitForProcess' for one or more processes that you+are most interested in. +Every function in this section (except for the 'pipeNone' function)+returns a value of type @(a, h)@, where @a@ is the set of 'Proxy',+and @h@ is the 'ProcessHandle'; that means the functions that return+multiple 'Proxy' have a nested return type. That allows you to use+'fst' and 'snd' to pull out the part you are interested in. It+would have been more consistent for 'pipeNone' to return+@((), ProcessHandle)@ but that just seemed silly.+ -} --- | Do not create any 'Proxy' to or from the process.-pipeNone- :: Pipes.Safe.MonadSafe m- => NonPipe- -- ^ Standard input- -> NonPipe- -- ^ Standard output- -> NonPipe- -- ^ Standard error- -> CreateProcess- -> m Process.ProcessHandle-pipeNone sIn sOut sErr cp = do- (_, _, _, han) <- createProcess (quiet cp) cp'- return han- where- cp' = convertCreateProcess (Just sIn) (Just sOut) (Just sErr)- cp+{- $reexports --- | Create a 'Consumer' for standard input.-pipeInput- :: (Pipes.Safe.MonadSafe mi, Pipes.Safe.MonadSafe m)- => NonPipe- -- ^ Standard output- -> NonPipe- -- ^ Standard error- -> CreateProcess- -> m (Consumer ByteString mi (), Process.ProcessHandle)- -- ^ A 'Consumer' for standard input, and the 'ProcessHandle'-pipeInput sOut sErr cp = do- (Just inp, _, _, han) <- createProcess (quiet cp) cp'- inp' <- makeToProcess (quiet cp) inp- return (inp', han)- where- cp' = convertCreateProcess Nothing (Just sOut) (Just sErr)- cp+ * "Control.Concurrent.MVar" reexports all bindings --- | Create a 'Producer' for standard output.-pipeOutput- :: (Pipes.Safe.MonadSafe mi, Pipes.Safe.MonadSafe m)- => NonPipe- -- ^ Standard input- -> NonPipe- -- ^ Standard error- -> CreateProcess- -> m (Producer ByteString mi (), Process.ProcessHandle)- -- ^ A 'Producer' for standard output, and the 'ProcessHandle'-pipeOutput sIn sErr cp = do- (_, Just out, _, han) <- createProcess (quiet cp) cp'- out' <- makeFromProcess (quiet cp) out- return (out', han)- where- cp' = convertCreateProcess (Just sIn) Nothing (Just sErr) cp+ * "Pipes" reexports all bindings --- | Create a 'Producer' for standard error.-pipeError- :: (Pipes.Safe.MonadSafe mi, Pipes.Safe.MonadSafe m)- => NonPipe- -- ^ Standard input- -> NonPipe- -- ^ Standard output- -> CreateProcess- -> m (Producer ByteString mi (), Process.ProcessHandle)- -- ^ A 'Producer' for standard error, and the 'ProcessHandle'-pipeError sIn sOut cp = do- (_, _, Just err, han) <- createProcess (quiet cp) cp'- err' <- makeFromProcess (quiet cp) err- return (err', han)- where- cp' = convertCreateProcess (Just sIn) (Just sOut) Nothing cp+ * "Pipes.Safe" reexports all bindings --- | Create a 'Consumer' for standard input and a 'Producer' for--- standard output.-pipeInputOutput- :: ( Pipes.Safe.MonadSafe mi, Pipes.Safe.MonadSafe mo- , Pipes.Safe.MonadSafe m)- => NonPipe- -- ^ Standard error- -> CreateProcess- -> m ( Consumer ByteString mi ()- , Producer ByteString mo ()- , Process.ProcessHandle- )- -- ^ A 'Consumer' for standard input, a 'Producer' for standard- -- output, and a 'ProcessHandle'-pipeInputOutput sErr cp = do- (Just inp, Just out, _, han) <- createProcess (quiet cp) cp'- inp' <- makeToProcess (quiet cp) inp- out' <- makeFromProcess (quiet cp) out- return (inp', out', han)- where- cp' = convertCreateProcess Nothing Nothing (Just sErr) cp+ * "System.Exit" reexports all bindings --- | Create a 'Consumer' for standard input and a 'Producer' for--- standard error.-pipeInputError- :: ( Pipes.Safe.MonadSafe mi, Pipes.Safe.MonadSafe mo- , Pipes.Safe.MonadSafe m)- => NonPipe- -- ^ Standard output- -> CreateProcess- -> m ( Consumer ByteString mi ()- , Producer ByteString mo ()- , Process.ProcessHandle- )- -- ^ A 'Consumer' for standard input, a 'Producer' for standard- -- error, and a 'ProcessHandle'-pipeInputError sOut cp = do- (Just inp, _, Just err, han) <- createProcess (quiet cp) cp'- inp' <- makeToProcess (quiet cp) inp- err' <- makeFromProcess (quiet cp) err- return (inp', err', han)- where- cp' = convertCreateProcess Nothing (Just sOut) Nothing cp+ * "System.Process" reexports 'ProcessHandle' --- | Create a 'Producer' for standard output and a 'Producer' for--- standard error.-pipeOutputError- :: ( Pipes.Safe.MonadSafe mi- , Pipes.Safe.MonadSafe mo- , Pipes.Safe.MonadSafe m)- => NonPipe- -- ^ Standard input- -> CreateProcess- -> m ( Producer ByteString mi ()- , Producer ByteString mo ()- , Process.ProcessHandle- )- -- ^ A 'Producer' for standard output, a 'Producer' for standard- -- error, and a 'ProcessHandle'-pipeOutputError sIn cp = do- (_, Just out, Just err, han) <- createProcess (quiet cp) cp'- out' <- makeFromProcess (quiet cp) out- err' <- makeFromProcess (quiet cp) err- return (out', err', han)- where- cp' = convertCreateProcess (Just sIn) Nothing Nothing cp+-} --- | Create a 'Consumer' for standard input, a 'Producer' for standard--- output, and a 'Producer' for standard error.-pipeInputOutputError- :: ( Pipes.Safe.MonadSafe mi, Pipes.Safe.MonadSafe mo,- Pipes.Safe.MonadSafe me, Pipes.Safe.MonadSafe m)- => CreateProcess- -> m ( Consumer ByteString mi ()- , Producer ByteString mo ()- , Producer ByteString me ()- , Process.ProcessHandle- )- -- ^ A 'Consumer' for standard input, a 'Producer' for standard- -- output, a 'Producer' for standard error, and a 'ProcessHandle'-pipeInputOutputError cp = do- (Just inp, Just out, Just err, han) <- createProcess (quiet cp) cp'- inp' <- makeToProcess (quiet cp) inp- out' <- makeFromProcess (quiet cp) out- err' <- makeFromProcess (quiet cp) err- return (inp', out', err', han)- where- cp' = convertCreateProcess Nothing Nothing Nothing cp+{- $designNotes -{- $reexports+Two overarching principles guided the design of this+library. First, I wanted the interface to use simple+ByteStrings. That most closely represents what a UNIX process+sees. If the user wants to use Text or String, it's easy+enough to convert between those types and a ByteString. Then+the user has to pay explicit attention to encoding issues--as+she should, because not all UNIX processes deal with encoded+textual data. - * "Pipes" reexports all bindings+Second, I paid meticulous attention to resource management.+Resources are deterministically destroyed immediately after+use. This eliminates many bugs. - * "Pipes.Safe" reexports 'runSafeT'+You might wonder why, if you are using an external process as+a pipeline, why can't you create, well, a 'Pipe'? Wouldn't+that be an obvious choice? Well, if such an interface is+possible using Pipes in its current incarnation, nobody has+figured it out yet. I don't think it's possible. See also - * "System.Exit" reexports all bindings+<https://groups.google.com/d/msg/haskell-pipes/JFfyquj5HAg/Lxz7p50JOh4J> - * "System.Process" reexports 'ProcessHandle'+for a discussion about this. -}
+ lib/Pipes/Cliff/Core.hs view
@@ -0,0 +1,709 @@+{-# LANGUAGE FlexibleContexts #-}++-- | This contains the innards of Cliff. You probably won't need+-- anything that's in here; "Pipes.Cliff" re-exports the most useful+-- bindings. But nothing will break if you use what's in here, so+-- it's here if you need it.+module Pipes.Cliff.Core where++import System.Environment+import Data.List (intersperse)+import Control.Exception (IOException)+import System.IO+import qualified System.Process as Process+import System.Process (ProcessHandle)+import Pipes+import Pipes.Safe+import qualified Data.ByteString as BS+import qualified Pipes.Concurrent as PC+import Data.ByteString (ByteString)+import Control.Concurrent.Async+import System.Exit++-- * Data types++-- | Like 'Process.CmdSpec' in "System.Process", but also has an+-- instance for 'Show'.+data CmdSpec+ = ShellCommand String+ | RawCommand FilePath [String]+ deriving (Eq, Ord, Show)++convertCmdSpec :: CmdSpec -> Process.CmdSpec+convertCmdSpec (ShellCommand s) = Process.ShellCommand s+convertCmdSpec (RawCommand p ss) = Process.RawCommand p ss++-- ** Errors++-- | When dealing with a 'Handle', errors can occur when reading from,+-- writing to, or closing the handle.+data Activity+ = Reading+ | Writing+ | Closing+ deriving (Eq, Ord, Show)++-- | Describes a handle. From the perspective of the subprocess; for+-- example, 'Input' means that this handle is connected to the+-- process's standard input.+data HandleDesc+ = Input+ | Output+ | Error+ deriving (Eq, Ord, Show)++-- | Describes IO errors tha occur when dealing with a 'Handle'.+data HandleOopsie = HandleOopsie Activity HandleDesc+ deriving (Eq,Show)++-- | Describes all IO exceptions. The 'Oopsie' contains the+-- 'IOException' itself, along with the 'CmdSpec' that was running+-- when the exception occurred. If the exception occurred while+-- dealing with a 'Handle', there is also a 'HandleOopsie'. If there+-- is no 'HandleOopsie', this means that the exception arose when+-- running 'terminateProcess'.+--+-- The exceptions that are caught and placed into an 'Oopsie' may+-- arise from reading data from or writing data to a 'Handle'. In+-- these errors, the associated 'Producer' or 'Consumer' will+-- terminate (which may trigger various cleanup actions in the+-- 'MonadSafe' computation) but the exception itself is not re-thrown;+-- rather, it is passed to the 'handler'. Similarly, an exception may+-- occur while closing a handle; these exceptions are caught, not+-- rethrown, and are passed to the 'handler'. If an exception arises+-- when terminating a process (I'm not sure this is possible) then it+-- is also caught, not rethrown, and passed to the 'handler'.+--+-- If an exception arises when creating a process--such as a command+-- not being found--the exception is /not/ caught, handled, or passed+-- to the 'handler'. Also, an 'Oopsie' is created only for an+-- 'IOException'; no other exceptions of any kind are caught or+-- handled. However, exceptions of any kind will still trigger+-- appropriate cleanup actions in the 'MonadSafe' computation.+data Oopsie = Oopsie (Maybe HandleOopsie) CmdSpec IOException+ deriving (Eq, Show)++-- | Formats an 'Oopsie' for display.+renderOopsie+ :: String+ -- ^ The name of the currently runnning program+ -> Oopsie+ -> String+renderOopsie pn (Oopsie mayHan cmd ioe) =+ pn ++ ": warning: when running command "+ ++ renderCommand cmd ++ ": " ++ renderMayHan mayHan+ ++ ": " ++ show ioe+ where+ renderCommand (ShellCommand str) = show str+ renderCommand (RawCommand fp ss)+ = concat . intersperse " " . map show+ $ fp : ss++ renderMayHan Nothing = "when terminating process"+ renderMayHan (Just (HandleOopsie act desc)) =+ "when " ++ actStr ++ " " ++ descStr+ where+ actStr = case act of+ Reading -> "reading from"+ Writing -> "writing to"+ Closing -> "closing the handle associated with"+ descStr = "standard " ++ case desc of+ Input -> "input"+ Output -> "output"+ Error -> "error"++-- | The default handler when receiving an 'Oopsie'; simply uses+-- 'renderOopsie' to format it nicely and put it on standard error.+defaultHandler :: Oopsie -> IO ()+defaultHandler oops = do+ pn <- getProgName+ hPutStrLn stderr $ renderOopsie pn oops++-- ** Configuration types++-- | How will the subprocess get its information for this stream? A+-- 'NonPipe' is used for streams that will not be assigned to a+-- 'Proxy' but, instead, will be inherited from the parent or directed+-- from an existing 'Handle'.+data NonPipe+ = Inherit+ -- ^ Use whatever stream that the parent process has.+ | UseHandle Handle+ -- ^ Use the given handle for input or output++convertNonPipe :: Maybe NonPipe -> Process.StdStream+convertNonPipe a = case a of+ Nothing -> Process.CreatePipe+ Just Inherit -> Process.Inherit+ Just (UseHandle h) -> Process.UseHandle h++-- | Like 'System.Process.CreateProcess' in "System.Process",+-- this gives the necessary information to create a subprocess. All+-- but one of these fields is also present in+-- 'System.Process.CreateProcess', and they all have the same meaning;+-- the only field that is different is the 'handler' field.+data CreateProcess = CreateProcess+ { cmdspec :: CmdSpec+ -- ^ Executable and arguments, or shell command++ , cwd :: Maybe FilePath+ -- ^ A new current working directory for the subprocess; if+ -- 'Nothing', use the calling process's working directory.++ , env :: Maybe [(String, String)]+ -- ^ The environment for the subprocess; if 'Nothing', use the+ -- calling process's working directory.++ , close_fds :: Bool+ -- ^ If 'True', close all file descriptors other than the standard+ -- descriptors. See the documentation for+ -- 'System.Process.close_fds' for details on how this works in+ -- Windows.++ , create_group :: Bool+ -- ^ If 'True', create a new process group.++ , delegate_ctlc :: Bool+ -- ^ See 'System.Process.delegate_ctlc' in the "System.Process"+ -- module for details.++ , handler :: Oopsie -> IO ()+ -- ^ Whenever an IO exception arises during the course of various+ -- IO actios, the exception is caught and placed into an 'Oopsie'+ -- that indicates why and where the exception happened. The+ -- 'handler' determines what happens when an 'Oopsie' comes in.+ -- See 'Oopsie' for details.+ --+ -- The default 'handler' created by 'procSpec' is+ -- 'defaultHandler', which will simply print the exceptions to+ -- standard error. You may not want to see the exceptions at all.+ -- For example, many exceptions come from broken pipes. A broken+ -- pipe might be entirely normal in your circumstance. For+ -- example, if you are streaming a large set of values to a pager+ -- such as @less@ and you expect that the user will often quit the+ -- pager without viewing the whole result, a broken pipe will+ -- result, which will print a warning message. That can be a+ -- nuisance.+ --+ -- If you don't want to see the exceptions at all, just set+ -- 'handler' to 'squelch', which simply discards the exceptions.+ --+ -- Conceivably you could rig up an elaborate mechanism that puts+ -- the 'Oopsie's into a "Pipes.Concurrent" mailbox or something.+ -- Indeed, when using 'defaultHandler' each thread will print its+ -- warnings to standard error at any time. If you are using+ -- multiple processes and each prints warnings at the same time,+ -- total gibberish can result as the text gets mixed in. You+ -- could solve this by putting the errors into a+ -- "Pipes.Concurrent" mailbox and having a single thread print the+ -- errors; building this sort of functionality directly in to the+ -- library would clutter up the API somewhat so I have been+ -- reluctant to do it.+ }++-- | Do not show or do anything with exceptions; useful to use as a+-- 'handler'.+squelch :: Monad m => a -> m ()+squelch = const (return ())++-- | Create a 'CreateProcess' record with default settings. The+-- default settings are:+--+-- * a raw command (as opposed to a shell command) is created+--+-- * the current working directory is not changed from the parent process+--+-- * the environment is not changed from the parent process+--+-- * the parent's file descriptors (other than standard input,+-- standard output, and standard error) are inherited+--+-- * no new process group is created+--+-- * 'delegate_ctlc' is 'False'+--+-- * 'storeProcessHandle' is 'Nothing'+--+-- * 'handler' is 'defaultHandler'++procSpec+ :: String+ -- ^ The name of the program to run, such as @less@.+ -> [String]+ -- ^ Command-line arguments+ -> CreateProcess+procSpec prog args = CreateProcess+ { cmdspec = RawCommand prog args+ , cwd = Nothing+ , env = Nothing+ , close_fds = False+ , create_group = False+ , delegate_ctlc = False+ , handler = defaultHandler+ }++convertCreateProcess+ :: Maybe NonPipe+ -> Maybe NonPipe+ -> Maybe NonPipe+ -> CreateProcess+ -> Process.CreateProcess+convertCreateProcess inp out err a = Process.CreateProcess+ { Process.cmdspec = convertCmdSpec $ cmdspec a+ , Process.cwd = cwd a+ , Process.env = env a+ , Process.std_in = conv inp+ , Process.std_out = conv out+ , Process.std_err = conv err+ , Process.close_fds = close_fds a+ , Process.create_group = create_group a+ , Process.delegate_ctlc = delegate_ctlc a+ }+ where+ conv = convertNonPipe++-- * ErrSpec++-- | Contains data necessary to deal with exceptions.+data ErrSpec = ErrSpec+ { esErrorHandler :: Oopsie -> IO ()+ , esCmdSpec :: CmdSpec+ }++makeErrSpec+ :: CreateProcess+ -> ErrSpec+makeErrSpec cp = ErrSpec+ { esErrorHandler = handler cp+ , esCmdSpec = cmdspec cp+ }+++-- * Exception handling++-- | Sends an exception using the exception handler specified in the+-- 'ErrSpec'.+handleException+ :: MonadIO m+ => Maybe HandleOopsie+ -> IOException+ -> ErrSpec+ -> m ()+handleException mayOops exc ev = liftIO $ sender oops+ where+ spec = esCmdSpec ev+ sender = esErrorHandler ev+ oops = Oopsie mayOops spec exc+++-- | Run an action, taking all IO errors and sending them to the handler.+handleErrors+ :: (MonadCatch m, MonadIO m)+ => Maybe HandleOopsie+ -> ErrSpec+ -> m ()+ -> m ()+handleErrors mayHandleOops ev act = catch act catcher+ where+ catcher e = liftIO $ hndlr oops+ where+ spec = esCmdSpec ev+ hndlr = esErrorHandler ev+ oops = Oopsie mayHandleOops spec e+++-- | Close a handle. Catches any exceptions and passes them to the handler.+closeHandleNoThrow+ :: (MonadCatch m, MonadIO m)+ => Handle+ -> HandleDesc+ -> ErrSpec+ -> m ()+closeHandleNoThrow hand desc ev = handleErrors (Just (HandleOopsie Closing desc))+ ev (liftIO $ hClose hand)++-- | Terminates a process; sends any IO errors to the handler.+terminateProcess+ :: (MonadCatch m, MonadIO m)+ => Process.ProcessHandle+ -> ErrSpec+ -> m ()+terminateProcess han ev = do+ _ <- handleErrors Nothing ev (liftIO (Process.terminateProcess han))+ handleErrors Nothing ev . liftIO $ do+ _ <- Process.waitForProcess han+ return ()+++-- | Acquires a resource and ensures it will be destroyed when the+-- 'MonadSafe' computation completes.+acquire+ :: MonadSafe m+ => Base m a+ -- ^ Acquirer.+ -> (a -> Base m ())+ -- ^ Destroyer.+ -> m a+acquire acq rel = mask $ \restore -> do+ a <- liftBase acq+ _ <- register (rel a)+ restore $ return a+++-- * Threads++-- | Runs a thread in the background. The thread is terminated when+-- the 'MonadSafe' computation completes.+background+ :: MonadSafe m+ => IO a+ -> m (Async a)+background act = acquire (liftIO $ async act) (liftIO . cancel)++-- | Runs in the background an effect, typically one that is moving+-- data from one process to another. For examples of its usage, see+-- "Pipes.Cliff.Examples". The associated thread is killed when the+-- 'MonadSafe' computation completes.+conveyor :: MonadSafe m => Effect (SafeT IO) () -> m ()+conveyor efct+ = (background . liftIO . runSafeT . runEffect $ efct) >> return ()+++-- | A version of 'Control.Concurrent.Async.wait' with an overloaded+-- 'MonadIO' return type. Allows you to wait for the return value of+-- threads launched with 'background'. If the thread throws an+-- exception, 'waitForThread' will throw that same exception.+waitForThread :: MonadIO m => Async a -> m a+waitForThread = liftIO . wait++-- | An overloaded version of the 'Process.waitForProcess' from+-- "System.Process".+waitForProcess :: MonadIO m => ProcessHandle -> m ExitCode+waitForProcess h = liftIO $ Process.waitForProcess h+++-- * Mailboxes++-- | A buffer that holds 1 message. I have no idea if this is the+-- ideal size. Don't use an unbounded buffer, though, because with+-- unbounded producers an unbounded buffer will fill up your RAM.+--+-- Since the buffer just holds one size, you might think \"why not+-- just use an MVar\"? At least, I have been silly enough to think+-- that. Using @Pipes.Concurrent@ also give the mailbox the ability+-- to be sealed; sealing the mailbox signals to the other side that it+-- won't be getting any more input or be allowed to send any more+-- output, which tells the whole pipeline to start shutting down.+messageBuffer :: PC.Buffer a+messageBuffer = PC.bounded 1++-- | Creates a new mailbox and returns 'Proxy' that stream values+-- into and out of the mailbox. Each 'Proxy' is equipped with a+-- finalizer that will seal the mailbox immediately after production+-- or consumption has completed, even if such completion is not due+-- to an exhausted mailbox. This will signal to the other side of+-- the mailbox that the mailbox is sealed.+newMailbox+ :: (MonadIO m, MonadSafe mi, MonadSafe mo)+ => m (Consumer a mi (), Producer a mo ())+newMailbox = do+ (toBox, fromBox, seal) <- liftIO $ PC.spawn' messageBuffer+ let csmr = register (liftIO $ PC.atomically seal)+ >> PC.toOutput toBox+ pdcr = register (liftIO $ PC.atomically seal)+ >> PC.fromInput fromBox+ return (csmr, pdcr)+++-- * Production from and consumption to 'Handle's++-- | I have no idea what this should be. I'll start with a simple+-- small value and see how it works.+bufSize :: Int+bufSize = 1024+++-- | Create a 'Producer' that produces from a 'Handle'. Takes+-- ownership of the 'Handle'; closes it when the 'Producer'+-- terminates. If any IO errors arise either during production or+-- when the 'Handle' is closed, they are caught and passed to the+-- handler.+produceFromHandle+ :: (MonadSafe m, MonadCatch (Base m))+ => HandleDesc+ -> Handle+ -> ErrSpec+ -> Producer ByteString m ()+produceFromHandle hDesc h ev = do+ _ <- register (closeHandleNoThrow h hDesc ev)+ let hndlr e = lift $ handleException (Just oops) e ev+ oops = HandleOopsie Reading hDesc+ produce = liftIO (BS.hGetSome h bufSize) >>= go+ go bs+ | BS.null bs = return ()+ | otherwise = yield bs >> produce+ produce `catch` hndlr+ ++-- | Runs a 'Consumer'; registers the handle so that it is closed+-- when consumption finishes. If any IO errors arise either during+-- consumption or when the 'Handle' is closed, they are caught and+-- passed to the handler.+consumeToHandle+ :: (MonadSafe m, MonadCatch (Base m))+ => Handle+ -> ErrSpec+ -> Consumer ByteString m ()+consumeToHandle h ev = do+ _ <- register $ closeHandleNoThrow h Input ev+ let hndlr e = lift $ handleException (Just oops) e ev+ oops = HandleOopsie Writing Input+ go = do+ bs <- await+ liftIO $ BS.hPut h bs+ go+ go `catch` hndlr+++-- | Creates a background thread that will consume to the given Handle+-- from the given Producer. Takes ownership of the 'Handle' and+-- closes it when done.+backgroundSendToProcess+ :: MonadSafe m+ => Handle+ -> Producer ByteString (SafeT IO) ()+ -> ErrSpec+ -> m ()+backgroundSendToProcess han prod ev = background act >> return ()+ where+ csmr = consumeToHandle han ev+ act = runSafeT . runEffect $ prod >-> csmr++-- | Creates a background thread that will produce from the given+-- Handle into the given Consumer. Takes possession of the Handle and+-- closes it when done.+backgroundReceiveFromProcess+ :: MonadSafe m+ => HandleDesc+ -> Handle+ -> Consumer ByteString (SafeT IO) ()+ -> ErrSpec+ -> m ()+backgroundReceiveFromProcess desc han csmr ev = background act >> return ()+ where+ prod = produceFromHandle desc han ev+ act = runSafeT . runEffect $ prod >-> csmr++-- | Does everything necessary to run a 'Handle' that is created to a+-- process standard input. Creates mailbox, runs background thread+-- that pumps data out of the mailbox and into the process standard+-- input, and returns a Consumer that consumes and places what it+-- consumes into the mailbox for delivery to the background process.+runInputHandle+ :: (MonadSafe m, MonadSafe mi)+ => Handle+ -> ErrSpec+ -> m (Consumer ByteString mi ())+runInputHandle inp ev = do+ (toMbox, fromMbox) <- liftIO newMailbox+ backgroundSendToProcess inp fromMbox ev+ return toMbox+++-- | Does everything necessary to run a 'Handle' that is created to a+-- process standard output or standard error. Creates mailbox, runs+-- background thread that pumps data from the process output 'Handle'+-- into the mailbox, and returns a Producer that produces what comes+-- into the mailbox.+runOutputHandle+ :: (MonadSafe m, MonadSafe mo)+ => HandleDesc+ -> Handle+ -> ErrSpec+ -> m (Producer ByteString mo ())+runOutputHandle desc out ev = do+ (toMbox, fromMbox) <- liftIO $ newMailbox+ backgroundReceiveFromProcess desc out toMbox ev+ return fromMbox+++-- * Creating subprocesses+++-- | Creates a subprocess. Registers destroyers for each handle+-- created, as well as for the ProcessHandle.+createProcess+ :: (MonadSafe m, MonadCatch (Base m))+ => Process.CreateProcess+ -> ErrSpec+ -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)+createProcess cp ev = mask $ \restore -> do+ (mayIn, mayOut, mayErr, han) <- liftIO $ Process.createProcess cp+ let close mayHan desc = maybe (return ())+ (\h -> closeHandleNoThrow h desc ev) mayHan+ _ <- register (close mayIn Input)+ _ <- register (close mayOut Output)+ _ <- register (close mayErr Error)+ _ <- register (terminateProcess han ev)+ restore $ return (mayIn, mayOut, mayErr, han)+++-- | Convenience wrapper for 'createProcess'. The subprocess is+-- terminated and all its handles destroyed when the 'MonadSafe'+-- computation completes.+runCreateProcess+ :: (MonadSafe m, MonadCatch (Base m))+ => Maybe NonPipe+ -- ^ Standard input+ -> Maybe NonPipe+ -- ^ Standard output+ -> Maybe NonPipe+ -- ^ Standard error+ -> CreateProcess+ -> m (Maybe Handle, Maybe Handle, Maybe Handle, ErrSpec, ProcessHandle)+runCreateProcess inp out err cp = do+ let ev = makeErrSpec cp+ (inp', out', err', phan) <-+ createProcess (convertCreateProcess inp out err cp) ev+ return (inp', out', err', ev, phan)++-- * Creating Proxy+++-- | Do not create any 'Proxy' to or from the process.+pipeNone+ :: (MonadSafe m, MonadCatch (Base m))+ => NonPipe+ -- ^ Standard input+ -> NonPipe+ -- ^ Standard output+ -> NonPipe+ -- ^ Standard error+ -> CreateProcess+ -> m ProcessHandle+pipeNone inp out err cp = do+ (_, _, _, _, phan) <-+ runCreateProcess (Just inp) (Just out) (Just err) cp+ return phan+++-- | Create a 'Consumer' for standard input.+pipeInput+ :: (MonadSafe mi, MonadSafe m, MonadCatch (Base m))+ => NonPipe+ -- ^ Standard output+ -> NonPipe+ -- ^ Standard error+ -> CreateProcess+ -> m (Consumer ByteString mi (), ProcessHandle)+ -- ^ A 'Consumer' for standard input+pipeInput out err cp = do+ (Just inp, _, _, ev, phan) <-+ runCreateProcess Nothing (Just out) (Just err) cp+ ih <- runInputHandle inp ev+ return (ih, phan)++++-- | Create a 'Producer' for standard output.+pipeOutput+ :: (MonadSafe mo, MonadSafe m, MonadCatch (Base m))+ => NonPipe+ -- ^ Standard input+ -> NonPipe+ -- ^ Standard error+ -> CreateProcess+ -> m (Producer ByteString mo (), ProcessHandle)+ -- ^ A 'Producer' for standard output+pipeOutput inp err cp = do+ (_, Just out, _, ev, phan) <- runCreateProcess (Just inp)+ Nothing (Just err) cp+ oh <- runOutputHandle Output out ev+ return (oh, phan)++-- | Create a 'Producer' for standard error.+pipeError+ :: (MonadSafe m, MonadCatch (Base m))+ => NonPipe+ -- ^ Standard input+ -> NonPipe+ -- ^ Standard output+ -> CreateProcess+ -> m (Producer ByteString m (), ProcessHandle)+ -- ^ A 'Producer' for standard error+pipeError inp out cp = do+ (_, _, Just err, ev, phan) <- runCreateProcess (Just inp) (Just out) Nothing cp+ eh <- runOutputHandle Error err ev+ return (eh, phan)++-- | Create a 'Consumer' for standard input and a 'Producer' for+-- standard output.+pipeInputOutput+ :: (MonadSafe mi, MonadSafe mo, MonadSafe m, MonadCatch (Base m))+ => NonPipe+ -- ^ Standard error+ -> CreateProcess+ -> m ((Consumer ByteString mi (), Producer ByteString mo ()), ProcessHandle)+ -- ^ A 'Consumer' for standard input, a 'Producer' for standard+ -- output+pipeInputOutput err cp = do+ (Just inp, Just out, _, ev, phan) <-+ runCreateProcess Nothing Nothing (Just err) cp+ ih <- runInputHandle inp ev+ oh <- runOutputHandle Output out ev+ return ((ih, oh), phan)++-- | Create a 'Consumer' for standard input and a 'Producer' for+-- standard error.+pipeInputError+ :: (MonadSafe mi, MonadSafe me, MonadSafe m, MonadCatch (Base m))+ => NonPipe+ -- ^ Standard output+ -> CreateProcess+ -> m ( (Consumer ByteString mi (), Producer ByteString me ())+ , ProcessHandle)+ -- ^ A 'Consumer' for standard input, a 'Producer' for standard+ -- error+pipeInputError out cp = do+ (Just inp, _, Just err, ev, phan) <-+ runCreateProcess Nothing (Just out) Nothing cp+ ih <- runInputHandle inp ev+ eh <- runOutputHandle Error err ev+ return $ ((ih, eh), phan)++-- | Create a 'Producer' for standard output and a 'Producer' for+-- standard error.+pipeOutputError+ :: (MonadSafe mo, MonadSafe me, MonadSafe m, MonadCatch (Base m))+ => NonPipe+ -- ^ Standard input+ -> CreateProcess+ -> m ((Producer ByteString mo (), Producer ByteString me ()), ProcessHandle)+ -- ^ A 'Producer' for standard output, a 'Producer' for standard+ -- error+pipeOutputError inp cp = do+ (_, Just out, Just err, ev, phan) <-+ runCreateProcess (Just inp) Nothing Nothing cp+ oh <- runOutputHandle Output out ev+ eh <- runOutputHandle Error err ev+ return ((oh, eh), phan)+++-- | Create a 'Consumer' for standard input, a 'Producer' for standard+-- output, and a 'Producer' for standard error.+pipeInputOutputError+ :: ( MonadSafe mi, MonadSafe mo, MonadSafe me,+ MonadSafe m, MonadCatch (Base m))+ => CreateProcess+ -> m (( Consumer ByteString mi ()+ , Producer ByteString mo ()+ , Producer ByteString me ()), ProcessHandle)+ -- ^ A 'Consumer' for standard input, a 'Producer' for standard+ -- output, a 'Producer' for standard error+pipeInputOutputError cp = do+ (Just inp, Just out, Just err, ev, phan) <-+ runCreateProcess Nothing Nothing Nothing cp+ ih <- runInputHandle inp ev+ oh <- runOutputHandle Output out ev+ eh <- runOutputHandle Error err ev+ return $ ((ih, oh, eh), phan)
lib/Pipes/Cliff/Examples.hs view
@@ -7,9 +7,15 @@ -- -- <https://github.com/massysett/pipes-cliff/blob/master/lib/Pipes/Cliff/Examples.hs> ----- __Be sure to use the @-threaded@ option__ when compiling code that--- uses "Pipes.Cliff", including this code; see the warning in+-- __Be sure to use the @-threaded@ option__ when compiling code+-- that uses "Pipes.Cliff", including this code; see the warning in -- "Pipes.Cliff" for more details.+--+-- Notice throughout how pipelines that move data from one process+-- to another typically are run in the background using 'conveyor',+-- and that threads that produce information you need to use are run+-- in the 'background' so you can use 'waitForThread' to retrieve+-- their results. module Pipes.Cliff.Examples where @@ -24,7 +30,8 @@ -- can use "Pipes.Cliff" even for non-finite 'Producer's. Don't try -- to go to the end of the input in @less@, though. When you quit -- @less@, you will get broken pipe warnings printed to standard--- error. This is normal. To suppress them, see the 'quiet' option.+-- error. This is normal. To suppress them, see the 'handler'+-- option. numsToLess :: IO ExitCode numsToLess = runSafeT $ do@@ -32,32 +39,22 @@ conveyor $ produceNumbers >-> toLess waitForProcess han + -- | Streams an infinite list of numbers to @tr@ and then to @less@.--- Perfectly useless, but shows how to build pipelines. Notice how--- the components of the pipeline are run in the background using--- 'conveyor'; if you run one of them in the foreground, you might get--- a deadlock.+-- Perfectly useless, but shows how to build pipelines. Also+-- squlches warning messages using the 'handler' option. alphaNumbers :: IO ExitCode alphaNumbers = runSafeT $ do- (toTr, fromTr, _) <- pipeInputOutput Inherit- (procSpec "tr" ["[0-9]", "[a-z]"])+ ((toTr, fromTr), _) <- pipeInputOutput Inherit+ (procSpec "tr" ["[0-9]", "[a-z]"]) { handler = squelch } (toLess, lessHan) <- pipeInput Inherit Inherit- (procSpec "less" [])+ (procSpec "less" []) { handler = squelch } conveyor $ produceNumbers >-> toTr conveyor $ fromTr >-> toLess waitForProcess lessHan --- | Produces a stream of 'BS8.ByteString', where each--- 'BS8.ByteString' is a shown integer.--produceNumbers :: Monad m => Producer BS8.ByteString m ()-produceNumbers = each . fmap mkNumStr $ [(0 :: Int) ..]- where- mkNumStr = flip BS8.snoc '\n' . BS8.pack . show-- -- | Produces an infinite stream of numbers, sends it to @tr@ for some -- mangling, and then to @sh@, which will copy each line both to -- standard output and to standard error. From @sh@, standard output@@ -74,29 +71,30 @@ -- output the user actually viewed in @less@. standardOutputAndError :: IO BS8.ByteString standardOutputAndError = runSafeT $ do- (toTr, fromTr, _) <- pipeInputOutput Inherit+ ((toTr, fromTr), _) <- pipeInputOutput Inherit (procSpec "tr" ["[0-9]", "[a-z]"])- (toSh, fromShOut, fromShErr, _) <- pipeInputOutputError+ ((toSh, fromShOut, fromShErr), _) <- pipeInputOutputError (procSpec "sh" ["-c", script])- (toLess, _) <- pipeInput Inherit Inherit- (procSpec "less" [])+ (toLess, lessHan) <- pipeInput Inherit Inherit (procSpec "less" []) conveyor $ produceNumbers >-> toTr conveyor $ fromTr >-> toSh conveyor $ fromShOut >-> toLess- thread <- background+ foldHan <-+ background . runSafeT- . P.fold BS8.append BS8.empty id- $ fromShErr- waitForThread thread+ $ P.fold BS8.append BS8.empty id fromShErr+ _ <- waitForProcess lessHan+ waitForThread foldHan where script = "while read line; do echo $line; echo $line 1>&2; done" -- | Like 'alphaNumbers' but just sends a limited number -- of numbers to @cat@. A useful test to make sure that pipelines--- shut down automatically.+-- shut down automatically. Runs both pipelines in the background and+-- uses 'waitForProcess' to wait until @cat@ is done. limitedAlphaNumbers :: IO ExitCode limitedAlphaNumbers = runSafeT $ do- (toTr, fromTr, _) <- pipeInputOutput Inherit+ ((toTr, fromTr), _) <- pipeInputOutput Inherit (procSpec "tr" ["[0-9]", "[a-z]"]) (toCat, catHan) <- pipeInput Inherit Inherit (procSpec "cat" [])@@ -106,25 +104,27 @@ -- | Produces a finite list of numbers, sends it to @tr@ for some -- mangling, and then puts the results into a 'BS8.ByteString' for--- further processing. Unlike previous examples, there is no use of--- 'waitForProcess' or 'conveyor'. This is OK because the 'Effect'--- that retrieves the results from @tr@ will pull all the data; the--- @tr@ process will then shut down because its standard input will be--- closed when the source 'Producer' is exhausted. This example shows--- how you can use this library to place the results of a pipeline--- into a simple strict data type.------ When the 'SafeT' computation completes, a--- 'System.Process.terminateProcess' is automatically sent to the @tr@--- process--which does nothing, as @tr@ has already died. Only after--- the process is waited for is it fully removed from the system--- process table. A 'System.Process.waitForProcess' from--- "System.Process" is automatically done as well. Therefore, you--- will not get zombie processes if you use this library.+-- further processing. This example shows how you can use this+-- library to place the results of a pipeline into a simple strict+-- data type. alphaNumbersByteString :: IO BS8.ByteString alphaNumbersByteString = runSafeT $ do- (toTr, fromTr, _) <- pipeInputOutput Inherit+ ((toTr, fromTr), _) <- pipeInputOutput Inherit (procSpec "tr" ["[0-9]", "[a-z]"]) conveyor $ produceNumbers >-> P.take 300 >-> toTr- P.fold BS8.append BS8.empty id fromTr+ threadHan <-+ background+ . runSafeT+ $ P.fold BS8.append BS8.empty id fromTr+ waitForThread threadHan+++-- | Produces a stream of 'BS8.ByteString', where each+-- 'BS8.ByteString' is a shown integer.++produceNumbers :: Monad m => Producer BS8.ByteString m ()+produceNumbers = each . fmap mkNumStr $ [(0 :: Int) ..]+ where+ mkNumStr = flip BS8.snoc '\n' . BS8.pack . show+
pipes-cliff.cabal view
@@ -3,11 +3,11 @@ -- http://www.github.com/massysett/cartel -- -- Script name used to generate: genCabal.hs--- Generated on: 2015-03-22 13:30:11.54764 EDT+-- Generated on: 2015-03-24 13:45:58.663565 EDT -- Cartel library version: 0.14.2.0 name: pipes-cliff-version: 0.4.0.0+version: 0.6.0.0 cabal-version: >= 1.16 license: BSD3 license-file: LICENSE@@ -45,6 +45,7 @@ Library exposed-modules: Pipes.Cliff+ Pipes.Cliff.Core Pipes.Cliff.Examples hs-source-dirs: lib@@ -55,7 +56,7 @@ , bytestring >= 0.10.4 && < 0.11 , process >= 1.2.0.0 && < 1.3 , async >= 2.0 && < 2.1- , pipes-concurrency >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0.3 && < 2.1 default-language: Haskell2010 ghc-options: -Wall@@ -76,6 +77,7 @@ tests other-modules: Pipes.Cliff+ Pipes.Cliff.Core Pipes.Cliff.Examples build-depends: base >= 4.6.0.0 && < 4.8@@ -84,7 +86,7 @@ , bytestring >= 0.10.4 && < 0.11 , process >= 1.2.0.0 && < 1.3 , async >= 2.0 && < 2.1- , pipes-concurrency >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0.3 && < 2.1 ghc-options: -threaded else@@ -102,6 +104,7 @@ tests other-modules: Pipes.Cliff+ Pipes.Cliff.Core Pipes.Cliff.Examples build-depends: base >= 4.6.0.0 && < 4.8@@ -110,7 +113,7 @@ , bytestring >= 0.10.4 && < 0.11 , process >= 1.2.0.0 && < 1.3 , async >= 2.0 && < 2.1- , pipes-concurrency >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0.3 && < 2.1 ghc-options: -threaded else@@ -128,6 +131,7 @@ tests other-modules: Pipes.Cliff+ Pipes.Cliff.Core Pipes.Cliff.Examples build-depends: base >= 4.6.0.0 && < 4.8@@ -136,7 +140,7 @@ , bytestring >= 0.10.4 && < 0.11 , process >= 1.2.0.0 && < 1.3 , async >= 2.0 && < 2.1- , pipes-concurrency >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0.3 && < 2.1 ghc-options: -threaded else@@ -154,6 +158,7 @@ tests other-modules: Pipes.Cliff+ Pipes.Cliff.Core Pipes.Cliff.Examples build-depends: base >= 4.6.0.0 && < 4.8@@ -162,7 +167,7 @@ , bytestring >= 0.10.4 && < 0.11 , process >= 1.2.0.0 && < 1.3 , async >= 2.0 && < 2.1- , pipes-concurrency >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0.3 && < 2.1 ghc-options: -threaded else@@ -180,6 +185,7 @@ tests other-modules: Pipes.Cliff+ Pipes.Cliff.Core Pipes.Cliff.Examples build-depends: base >= 4.6.0.0 && < 4.8@@ -188,7 +194,7 @@ , bytestring >= 0.10.4 && < 0.11 , process >= 1.2.0.0 && < 1.3 , async >= 2.0 && < 2.1- , pipes-concurrency >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0.3 && < 2.1 ghc-options: -threaded else