packages feed

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 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:++[![Build Status](https://travis-ci.org/massysett/pipes-cliff.svg?branch=master)](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