diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb343a..c88449c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Allow streamly-0.10.0 and streamly-core-0.2.0 * Fix a bug in quote escaping in the Command module +* Add APIs in System.Process and System.Command module with ability to set + process attributes. ## 0.3.0 (Apr 2023) diff --git a/src/Streamly/Internal/System/Command.hs b/src/Streamly/Internal/System/Command.hs index 26f63d4..fd8d402 100644 --- a/src/Streamly/Internal/System/Command.hs +++ b/src/Streamly/Internal/System/Command.hs @@ -15,6 +15,7 @@ module Streamly.Internal.System.Command -- * Generation toBytes , toChunks + , toChunksWith , toChars , toLines @@ -27,6 +28,12 @@ module Streamly.Internal.System.Command , pipeBytes , pipeChars , pipeChunks + , pipeChunksWith + + -- * Standalone Processes + , standalone + , foreground + , daemon -- * Helpers , quotedWord @@ -43,6 +50,9 @@ import Streamly.Data.Array (Array) import Streamly.Data.Fold (Fold) import Streamly.Data.Parser (Parser) import Streamly.Data.Stream.Prelude (MonadAsync, Stream) +import Streamly.Internal.System.Process (Config) +import System.Exit (ExitCode(..)) +import System.Process (ProcessHandle) import qualified Streamly.Data.Fold as Fold import qualified Streamly.Data.Parser as Parser @@ -157,6 +167,15 @@ pipeWith f cmd input = y:ys -> return $ f y ys input _ -> error "streamWith: empty command" +-- | Like 'pipeChunks' but use the specified configuration to run the process. +{-# INLINE pipeChunksWith #-} +pipeChunksWith :: + (MonadCatch m, MonadAsync m) + => (Config -> Config) -- ^ Config modifier + -> String -- ^ Command + -> Stream m (Array Word8) -- ^ Input stream + -> Stream m (Array Word8) -- ^ Output stream +pipeChunksWith modifier = pipeWith (Process.pipeChunksWith modifier) -- | @pipeChunks command input@ runs the executable with arguments specified by -- @command@ and supplying @input@ stream as its standard input. Returns the @@ -241,6 +260,15 @@ pipeChars = pipeWith Process.pipeChars toBytes :: (MonadAsync m, MonadCatch m) => String -> Stream m Word8 toBytes = streamWith Process.toBytes +-- | Like 'toChunks' but use the specified configuration to run the process. +{-# INLINE toChunksWith #-} +toChunksWith :: + (MonadCatch m, MonadAsync m) + => (Config -> Config) -- ^ Config modifier + -> String -- ^ Command + -> Stream m (Array Word8) -- ^ Output stream +toChunksWith modifier = streamWith (Process.toChunksWith modifier) + -- >>> toChunks = streamWith Process.toChunks -- | @@ -318,3 +346,62 @@ toNull :: => String -- ^ Command -> m () toNull = runWith Process.toNull + +------------------------------------------------------------------------------- +-- Processes not interacting with the parent process +------------------------------------------------------------------------------- + +-- | Launch a standlone process i.e. the process does not have a way to attach +-- the IO streams with other processes. The IO streams stdin, stdout, stderr +-- can either be inherited from the parent or closed. +-- +-- This API is more powerful than 'interactive' and 'daemon' and can be used to +-- implement both of these. However, it should be used carefully e.g. if you +-- inherit the IO streams and parent is not waiting for the child process to +-- finish then both parent and child may use the IO streams resulting in +-- garbled IO if both are reading/writing simultaneously. +-- +-- If the parent chooses to wait for the process an 'ExitCode' is returned +-- otherwise a 'ProcessHandle' is returned which can be used to terminate the +-- process, send signals to it or wait for it to finish. +{-# INLINE standalone #-} +standalone :: + Bool -- ^ Wait for process to finish? + -> (Bool, Bool, Bool) -- ^ close (stdin, stdout, stderr) + -> (Config -> Config) + -> String -- ^ Command + -> IO (Either ExitCode ProcessHandle) +standalone wait streams modCfg = + runWith (Process.standalone wait streams modCfg) + +-- | Launch a process interfacing with the user. User interrupts are sent to +-- the launched process and ignored by the parent process. The launched process +-- inherits stdin, stdout, and stderr from the parent, so that the user can +-- interact with the process. The parent waits for the child process to exit, +-- an 'ExitCode' is returned when the process finishes. +-- +-- This is the same as the common @system@ function found in other libraries +-- used to execute commands. +-- +-- On Windows you can pass @setSession NewConsole@ to create a new console. +-- +{-# INLINE foreground #-} +foreground :: + (Config -> Config) + -> String -- ^ Command + -> IO ExitCode +foreground modCfg = runWith (Process.foreground modCfg) + +-- | Launch a daemon process. Closes stdin, stdout and stderr, creates a new +-- session, detached from the terminal, the parent does not wait for the +-- process to finish. +-- +-- The 'ProcessHandle' returned can be used to terminate the daemon or send +-- signals to it. +-- +{-# INLINE daemon #-} +daemon :: + (Config -> Config) + -> String -- ^ Command + -> IO ProcessHandle +daemon modCfg = runWith (Process.daemon modCfg) diff --git a/src/Streamly/System/Command.hs b/src/Streamly/System/Command.hs index c4bcae1..839fc3b 100644 --- a/src/Streamly/System/Command.hs +++ b/src/Streamly/System/Command.hs @@ -94,12 +94,36 @@ module Streamly.System.Command -- -- $setup - -- * Types - ProcessFailure (..) + -- * Exceptions + Process.ProcessFailure (..) + + -- * Process Configuration + -- | Use the config modifiers to modify the default config. + , Process.Config + + -- ** Common Modifiers + -- | These options apply to both POSIX and Windows. + , Process.setCwd + , Process.setEnv + , Process.closeFiles + , Process.newProcessGroup + , Process.Session (..) + , Process.setSession + + -- ** Posix Only Modifiers + -- | These options have no effect on Windows. + , Process.interruptChildOnly + , Process.setUserId + , Process.setGroupId + + -- ** Windows Only Modifiers + -- | These options have no effect on Posix. + , Process.waitForDescendants -- * Generation , toBytes , toChunks + , toChunksWith , toChars , toLines @@ -109,9 +133,27 @@ module Streamly.System.Command , toNull -- * Transformation + , pipeChunks + , pipeChunksWith , pipeBytes , pipeChars - , pipeChunks + + -- -- * Including Stderr Stream + -- | Like other "Generation" routines but along with stdout, stderr is also + -- included in the output stream. stdout is converted to 'Right' values in + -- the output stream and stderr is converted to 'Left' values. + -- , toBytesEither + -- , toChunksEither + -- , toChunksEitherWith + -- , pipeBytesEither + -- , pipeChunksEither + -- , pipeChunksEitherWith + + -- * Non-streaming Processes + -- | These processes do not attach the IO streams with other processes. + , foreground + , daemon + , standalone -- -- * Helpers -- , runWith @@ -121,7 +163,7 @@ module Streamly.System.Command where import Streamly.Internal.System.Command -import Streamly.Internal.System.Process (ProcessFailure (..)) +import qualified Streamly.Internal.System.Process as Process -- (ProcessFailure (..)) -- Keep it synced with the Internal module diff --git a/src/Streamly/System/Process.hs b/src/Streamly/System/Process.hs index ff5170b..9def8d8 100644 --- a/src/Streamly/System/Process.hs +++ b/src/Streamly/System/Process.hs @@ -123,6 +123,8 @@ module Streamly.System.Process , toBytes , toChars , toLines + + -- * Effects , toString , toStdout , toNull