pipes-cliff (empty) → 0.2.0.0
raw patch · 10 files changed
+1102/−0 lines, 10 filesdep +asyncdep +basedep +bytestringsetup-changed
Dependencies added: async, base, bytestring, pipes, pipes-concurrency, pipes-safe, process
Files
- LICENSE +31/−0
- README.md +84/−0
- Setup.hs +2/−0
- lib/Pipes/Cliff.hs +687/−0
- lib/Pipes/Cliff/Examples.hs +96/−0
- pipes-cliff.cabal +174/−0
- tests/alphaNumbers.hs +7/−0
- tests/alphaNumbersByteString.hs +7/−0
- tests/limitedAlphaNumbers.hs +7/−0
- tests/numsToLess.hs +7/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2015 Omari Norman.+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in+ the documentation and/or other materials provided with the+ distribution.++ * Neither the name of Omari Norman nor the names of contributors+ to this software may be used to endorse or promote products+ derived from this software without specific prior written+ permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,84 @@+# pipes-cliff++pipes-cliff helps you spawn subprocesses and send data to and+from them with the Pipes library. Subprocesses are opened using the+process library, and the processes and handles are properly cleaned+up even if there are exceptions.++Though this library uses the Pipes library, I have not coordinated+with the author of the Pipes library in any way. Any bugs or design+flaws are mine and should be reported to++http://www.github.com/massysett/pipes-cliff/issues++Though I hope pipes-cliff works on Windows systems, I have only+tested it on Unix-like systems. Any reports on how this library works+on Windows are appreciated.++## Building pipes-cliff++If you get the files from Hackage, just build them in the usual way+using cabal-install.++To build the files from Github, you will need to generate the Cabal+file. The Cabal file is generated using the Cartel package, so first+run++cabal install cartel++and then, from the main directory (the one with the generate script),+run++sh generate++(at least, this is how you would do it on a Unix-like system.)++If you are having trouble getting pipes-cliff to build, examine the+build history here:++[](https://travis-ci.org/massysett/pipes-cliff)++Previous builds will show you the dependencies that were used in the+past to build the library.++## Why the name?++It's named after Cliff Clavin, the mailman on the TV show+*Cheers*. pipes-cliff uses mailboxes to send information to and+from subprocesses.++The obvious name, pipes-process, has already been taken. At the+time of this writing, I saw at least two libraries with this name,+though neither was posted to Hackage.++## Similar libraries++Take a look at these other libraries; they might meet your needs.++### Subprocesses generally++* process-streaming++http://hackage.haskell.org/package/process-streaming++* pipes-shell++https://hackage.haskell.org/package/pipes-shell++### Larger scripting frameworks++* HSH++https://hackage.haskell.org/package/HSH++* Turtle++https://hackage.haskell.org/package/turtle++* Shelly++https://hackage.haskell.org/package/shelly++Also, look at this discussion on the Pipes mailing list:++https://groups.google.com/d/msg/haskell-pipes/JFfyquj5HAg/Lxz7p50JOh4J
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lib/Pipes/Cliff.hs view
@@ -0,0 +1,687 @@+{-# LANGUAGE RankNTypes #-}+-- | Spawn subprocesses and interact with them using "Pipes"+--+-- The interface in this module deliberately resembles the interface+-- in "System.Process". However, one consequence of this is that you+-- will not want to have unqualified names from this module and from+-- "System.Process" in scope at the same time.+--+-- As in "System.Process", you create a subprocess by creating a+-- 'CreateProcess' record and then applying a function to that+-- record. Unlike "System.Process", you use functions such as+-- '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'.+--+-- __Use the @-threaded@ GHC option__ when compiling your programs or+-- when using GHCi. Internally, this module uses+-- 'System.Process.waitForProcess' from the "System.Process" module;+-- it's also quite likely that you will use this function when you+-- write code using this library. As the documentation for+-- 'waitForProcess' states, you must use the @-threaded@ option to+-- prevent every thread in the system from suspending when you use+-- 'waitForProcess'. So, if your program experiences deadlocks, be+-- sure you used the @-threaded@ option.+--+-- This module relies on the "Pipes", "Pipes.Safe", and+-- "System.Process" modules. You will want to have basic+-- familiarity with what all of those modules do before using this+-- module. It uses "Control.Concurrent.Async" and+-- "Pipes.Concurrent" behind the scenes; you don't need to know how+-- these work unless you're curious.+--+-- All communcation with subprocesses is done with strict+-- 'ByteString's. If you are dealing with textual data, the @text@+-- library has functions to convert a 'ByteString' to a @Text@; you+-- will want to look at @Data.Text.Encoding@.+--+-- Nobody would mistake this module for a shell; nothing beats the+-- shell as a language for starting other programs, as the shell is+-- designed for that. This module allows you to perform simple+-- streaming with subprocesses without leaving the comfort of Haskell.+-- Take a look at the README.md file, which is distributed with the+-- tarball or is available at Github at+--+-- <https://github.com/massysett/pipes-cliff>+--+-- 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".+module Pipes.Cliff+ ( -- * Specifying a subprocess's properties+ NonPipe(..)+ , CreateProcess(..)+ , procSpec++ -- * Creating processes+ -- $process+ , pipeNone+ , pipeInput+ , pipeOutput+ , pipeError+ , pipeInputOutput+ , pipeInputError+ , pipeOutputError+ , pipeInputOutputError++ -- * Conveniences++ -- | These make it a bit easier to write both simple and+ -- complicated pipelines.++ , conveyor+ , waitForProcess++ -- * Re-exports+ -- $reexports+ , module Pipes+ , module Pipes.Safe+ , module System.Exit+ , module System.Process+ ) where++import System.IO+import Pipes+import Pipes.Concurrent+import Control.Concurrent.Async (Async, async, cancel)+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 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+ :: (MonadIO m, Pipes.Safe.MonadCatch 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+ :: (MonadIO m, Pipes.Safe.MonadCatch 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, MonadIO 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, MonadIO 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.+background :: (Pipes.Safe.MonadSafe m, MonadIO 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+ :: (MonadIO m, 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, MonadIO 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, MonadIO mp, Pipes.Safe.MonadSafe m, MonadIO 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, MonadIO m, Pipes.Safe.MonadSafe mp, MonadIO 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+ :: (MonadIO m, 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++{- $process++Each of these functions creates a process. The process begins+running immediately in a separate process while your Haskell program+continues concurrently. A function is provided for each possible+combination of standard input, standard output, and standard error.+Use the 'NonPipe' type to describe what you want to do with streams+you do NOT want to create a stream for. For example, to create a+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+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'.++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".++-}++-- | Do not create any 'Proxy' to or from the process.+pipeNone+ :: (MonadIO m, 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++-- | Create a 'Consumer' for standard input.+pipeInput+ :: (MonadIO mi, Pipes.Safe.MonadSafe mi, MonadIO m, 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++-- | Create a 'Producer' for standard output.+pipeOutput+ :: (MonadIO mi, Pipes.Safe.MonadSafe mi, MonadIO m, 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++-- | Create a 'Producer' for standard error.+pipeError+ :: (MonadIO mi, Pipes.Safe.MonadSafe mi, MonadIO m, 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++-- | Create a 'Consumer' for standard input and a 'Producer' for+-- standard output.+pipeInputOutput+ :: ( MonadIO mi, Pipes.Safe.MonadSafe mi, MonadIO mo, Pipes.Safe.MonadSafe mo+ , MonadIO m, 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++-- | Create a 'Consumer' for standard input and a 'Producer' for+-- standard error.+pipeInputError+ :: ( MonadIO mi, Pipes.Safe.MonadSafe mi, MonadIO mo, Pipes.Safe.MonadSafe mo+ , MonadIO m, 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++-- | Create a 'Producer' for standard output and a 'Producer' for+-- standard error.+pipeOutputError+ :: ( MonadIO mi, Pipes.Safe.MonadSafe mi, MonadIO mo+ , Pipes.Safe.MonadSafe mo, MonadIO m+ , 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+ :: ( MonadIO mi, Pipes.Safe.MonadSafe mi, MonadIO mo, Pipes.Safe.MonadSafe mo,+ MonadIO me, Pipes.Safe.MonadSafe me, MonadIO m, 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++{- $reexports++ * "Pipes" reexports all bindings++ * "Pipes.Safe" reexports 'runSafeT'++ * "System.Exit" reexports all bindings++ * "System.Process" reexports 'ProcessHandle'++-}
+ lib/Pipes/Cliff/Examples.hs view
@@ -0,0 +1,96 @@+-- | Examples using "Pipes.Cliff". You will want to look at the+-- source code itself; viewing just the Haddocks will not help you+-- much. You can view the source using Haddock if you used+-- @--hyperlink-source@ when building the library or if you are+-- viewing this on Hackage; look for the @Source@ link. Or, you can+-- find the source at+--+-- <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+-- "Pipes.Cliff" for more details.++module Pipes.Cliff.Examples where++import Pipes.Cliff+import qualified Pipes.Prelude as P+import qualified Data.ByteString.Char8 as BS8++-- | Streams an infinite list of numbers to @less@.+-- The 'Effect' that streams values to the process is run in the+-- background by using 'conveyor', even though there is only one+-- subprocess. This is typically what you want. Shows off how you+-- 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.++numsToLess :: IO ExitCode+numsToLess = runSafeT $ do+ (toLess, han) <- pipeInput Inherit Inherit (procSpec "less" [])+ 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.++alphaNumbers :: IO ExitCode+alphaNumbers = runSafeT $ do+ (toTr, fromTr, _) <- pipeInputOutput Inherit+ (procSpec "tr" ["[0-9]", "[a-z]"])+ (toLess, lessHan) <- pipeInput Inherit Inherit+ (procSpec "less" [])+ 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+++-- | Like 'alphaNumbers' but just sends a limited number+-- of numbers to @cat@. A useful test to make sure that pipelines+-- shut down automatically.+limitedAlphaNumbers :: IO ExitCode+limitedAlphaNumbers = runSafeT $ do+ (toTr, fromTr, _) <- pipeInputOutput Inherit+ (procSpec "tr" ["[0-9]", "[a-z]"])+ (toCat, catHan) <- pipeInput Inherit Inherit+ (procSpec "cat" [])+ conveyor $ produceNumbers >-> P.take 300 >-> toTr+ conveyor $ fromTr >-> toCat+ waitForProcess catHan++-- | 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.+alphaNumbersByteString :: IO BS8.ByteString+alphaNumbersByteString = runSafeT $ do+ (toTr, fromTr, _) <- pipeInputOutput Inherit+ (procSpec "tr" ["[0-9]", "[a-z]"])+ conveyor $ produceNumbers >-> P.take 300 >-> toTr+ P.fold BS8.append BS8.empty id fromTr
+ pipes-cliff.cabal view
@@ -0,0 +1,174 @@+-- This Cabal file generated using the Cartel library.+-- Cartel is available at:+-- http://www.github.com/massysett/cartel+--+-- Script name used to generate: genCabal.hs+-- Generated on: 2015-03-20 09:28:58.76288 EDT+-- Cartel library version: 0.14.2.0++name: pipes-cliff+version: 0.2.0.0+cabal-version: >= 1.16+license: BSD3+license-file: LICENSE+build-type: Simple+copyright: Copyright 2015 Omari Norman+author: Omari Norman+maintainer: omari@smileystation.com+stability: Experimental+homepage: http://www.github.com/massysett/pipes-cliff+bug-reports: http://www.github.com/massysett/pipes-cliff/issues+synopsis: Streaming to and from subprocesses using Pipes+description:+ pipes-cliff helps you spawn subprocesses and send data to and+ from them with the Pipes library.+ Subprocesses are opened using the+ process library, and the processes and handles are properly+ cleaned up even if there are exceptions.+ .+ Though this library uses the Pipes library, I have not coordinated+ with the author of the Pipes library in any way. Any bugs or design+ flaws are mine and should be reported to+ .+ <http://www.github.com/massysett/pipes-cliff/issues>+ .+ For more information, see the README.md file, which is located in the+ source tarball and at+ .+ <https://github.com/massysett/pipes-cliff>+category: Pipes, Concurrency+tested-with:+ GHC == 7.8.2+extra-source-files:+ README.md++Library+ exposed-modules:+ Pipes.Cliff+ Pipes.Cliff.Examples+ hs-source-dirs:+ lib+ build-depends:+ base >= 4.6.0.0 && < 4.8+ , pipes >= 4.1 && < 4.2+ , pipes-safe >= 2.2 && < 2.3+ , bytestring >= 0.10.4 && < 0.11+ , process >= 1.2.0.0 && < 1.3+ , async >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0 && < 2.1+ default-language: Haskell2010+ ghc-options:+ -Wall++source-repository head+ type: git+ location: https://github.com/massysett/pipes-cliff.git++Executable numsToLess+ main-is: numsToLess.hs+ if flag(tests)+ buildable: True+ else+ buildable: False+ default-language: Haskell2010+ ghc-options:+ -Wall+ hs-source-dirs:+ lib+ tests+ other-modules:+ Pipes.Cliff+ Pipes.Cliff.Examples+ build-depends:+ base >= 4.6.0.0 && < 4.8+ , pipes >= 4.1 && < 4.2+ , pipes-safe >= 2.2 && < 2.3+ , bytestring >= 0.10.4 && < 0.11+ , process >= 1.2.0.0 && < 1.3+ , async >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0 && < 2.1+ ghc-options:+ -threaded++Executable alphaNumbers+ main-is: alphaNumbers.hs+ if flag(tests)+ buildable: True+ else+ buildable: False+ default-language: Haskell2010+ ghc-options:+ -Wall+ hs-source-dirs:+ lib+ tests+ other-modules:+ Pipes.Cliff+ Pipes.Cliff.Examples+ build-depends:+ base >= 4.6.0.0 && < 4.8+ , pipes >= 4.1 && < 4.2+ , pipes-safe >= 2.2 && < 2.3+ , bytestring >= 0.10.4 && < 0.11+ , process >= 1.2.0.0 && < 1.3+ , async >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0 && < 2.1+ ghc-options:+ -threaded++Executable limitedAlphaNumbers+ main-is: limitedAlphaNumbers.hs+ if flag(tests)+ buildable: True+ else+ buildable: False+ default-language: Haskell2010+ ghc-options:+ -Wall+ hs-source-dirs:+ lib+ tests+ other-modules:+ Pipes.Cliff+ Pipes.Cliff.Examples+ build-depends:+ base >= 4.6.0.0 && < 4.8+ , pipes >= 4.1 && < 4.2+ , pipes-safe >= 2.2 && < 2.3+ , bytestring >= 0.10.4 && < 0.11+ , process >= 1.2.0.0 && < 1.3+ , async >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0 && < 2.1+ ghc-options:+ -threaded++Executable alphaNumbersByteString+ main-is: alphaNumbersByteString.hs+ if flag(tests)+ buildable: True+ else+ buildable: False+ default-language: Haskell2010+ ghc-options:+ -Wall+ hs-source-dirs:+ lib+ tests+ other-modules:+ Pipes.Cliff+ Pipes.Cliff.Examples+ build-depends:+ base >= 4.6.0.0 && < 4.8+ , pipes >= 4.1 && < 4.2+ , pipes-safe >= 2.2 && < 2.3+ , bytestring >= 0.10.4 && < 0.11+ , process >= 1.2.0.0 && < 1.3+ , async >= 2.0 && < 2.1+ , pipes-concurrency >= 2.0 && < 2.1+ ghc-options:+ -threaded++Flag tests+ description: Build test executables+ default: False+ manual: True
+ tests/alphaNumbers.hs view
@@ -0,0 +1,7 @@+module Main where++import Pipes.Cliff.Examples+import System.Exit++main :: IO ()+main = alphaNumbers >>= exitWith
+ tests/alphaNumbersByteString.hs view
@@ -0,0 +1,7 @@+module Main where++import Pipes.Cliff.Examples+import qualified Data.ByteString.Char8 as BS8++main :: IO ()+main = alphaNumbersByteString >>= BS8.putStr
+ tests/limitedAlphaNumbers.hs view
@@ -0,0 +1,7 @@+module Main where++import Pipes.Cliff.Examples+import System.Exit++main :: IO ()+main = limitedAlphaNumbers >>= exitWith
+ tests/numsToLess.hs view
@@ -0,0 +1,7 @@+module Main where++import Pipes.Cliff.Examples+import System.Exit++main :: IO ()+main = numsToLess >>= exitWith