polysemy-process 0.13.0.1 → 0.14.0.0
raw patch · 33 files changed
+516/−164 lines, 33 filesdep +asyncdep ~basedep ~incipit-coredep ~pathPVP ok
version bump matches the API change (PVP)
Dependencies added: async
Dependency ranges changed: base, incipit-core, path, path-io, polysemy, polysemy-conc, polysemy-plugin, polysemy-resume, polysemy-test, polysemy-time, posix-pty, process, stm-chans, tasty, tasty-expected-failure, typed-process, unix
API changes (from Hackage documentation)
+ Polysemy.Process: data Interrupt :: Effect
+ Polysemy.Process: interpretInterrupt :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
+ Polysemy.Process: interpretInterruptNull :: InterpreterFor Interrupt r
+ Polysemy.Process: interpretInterruptOnce :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
+ Polysemy.Process.Effect.Interrupt: [Interrupted] :: Interrupt m Bool
+ Polysemy.Process.Effect.Interrupt: [KillOnQuit] :: Text -> m a -> Interrupt m (Maybe a)
+ Polysemy.Process.Effect.Interrupt: [Quit] :: Interrupt m ()
+ Polysemy.Process.Effect.Interrupt: [Register] :: Text -> IO () -> Interrupt m ()
+ Polysemy.Process.Effect.Interrupt: [Unregister] :: Text -> Interrupt m ()
+ Polysemy.Process.Effect.Interrupt: [WaitQuit] :: Interrupt m ()
+ Polysemy.Process.Effect.Interrupt: data Interrupt :: Effect
+ Polysemy.Process.Effect.Interrupt: interrupted :: forall r_ad2q. Member Interrupt r_ad2q => Sem r_ad2q Bool
+ Polysemy.Process.Effect.Interrupt: killOnQuit :: forall r_ad2r a_X0. Member Interrupt r_ad2r => Text -> Sem r_ad2r a_X0 -> Sem r_ad2r (Maybe a_X0)
+ Polysemy.Process.Effect.Interrupt: killOnQuit_ :: Member Interrupt r => Text -> Sem r a -> Sem r ()
+ Polysemy.Process.Effect.Interrupt: quit :: forall r_ad2o. Member Interrupt r_ad2o => Sem r_ad2o ()
+ Polysemy.Process.Effect.Interrupt: register :: forall r_ad2j. Member Interrupt r_ad2j => Text -> IO () -> Sem r_ad2j ()
+ Polysemy.Process.Effect.Interrupt: unregister :: forall r_ad2m. Member Interrupt r_ad2m => Text -> Sem r_ad2m ()
+ Polysemy.Process.Effect.Interrupt: waitQuit :: forall r_ad2p. Member Interrupt r_ad2p => Sem r_ad2p ()
+ Polysemy.Process.Interpreter.Interrupt: interpretInterrupt :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
+ Polysemy.Process.Interpreter.Interrupt: interpretInterruptNull :: InterpreterFor Interrupt r
+ Polysemy.Process.Interpreter.Interrupt: interpretInterruptOnce :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
+ Polysemy.Process.Interpreter.Interrupt: interpretInterruptWith :: Members [Critical, Race, Async, Embed IO] r => ((SignalInfo -> IO ()) -> Handler) -> InterpreterFor Interrupt r
- Polysemy.Process.Effect.ProcessInput: encode :: forall a_adrc r_adv1. Member (ProcessInput a_adrc) r_adv1 => a_adrc -> Sem r_adv1 ByteString
+ Polysemy.Process.Effect.ProcessInput: encode :: forall a_adUY r_adYN. Member (ProcessInput a_adUY) r_adYN => a_adUY -> Sem r_adYN ByteString
- Polysemy.Process.Effect.ProcessOutput: chunk :: forall p_adza a_adzb r_adBu. Member (ProcessOutput p_adza a_adzb) r_adBu => ByteString -> ByteString -> Sem r_adBu ([a_adzb], ByteString)
+ Polysemy.Process.Effect.ProcessOutput: chunk :: forall p_ae2W a_ae2X r_ae5g. Member (ProcessOutput p_ae2W a_ae2X) r_ae5g => ByteString -> ByteString -> Sem r_ae5g ([a_ae2X], ByteString)
- Polysemy.Process.Effect.Pty: handle :: forall r_agXj. Member Pty r_agXj => Sem r_agXj Handle
+ Polysemy.Process.Effect.Pty: handle :: forall r_ahtI. Member Pty r_ahtI => Sem r_ahtI Handle
- Polysemy.Process.Effect.Pty: resize :: forall r_agXk. Member Pty r_agXk => Rows -> Cols -> Sem r_agXk ()
+ Polysemy.Process.Effect.Pty: resize :: forall r_ahtJ. Member Pty r_ahtJ => Rows -> Cols -> Sem r_ahtJ ()
- Polysemy.Process.Effect.Pty: size :: forall r_agXn. Member Pty r_agXn => Sem r_agXn (Rows, Cols)
+ Polysemy.Process.Effect.Pty: size :: forall r_ahtM. Member Pty r_ahtM => Sem r_ahtM (Rows, Cols)
- Polysemy.Process.Effect.SystemProcess: pid :: forall r_ai0Y. Member SystemProcess r_ai0Y => Sem r_ai0Y Pid
+ Polysemy.Process.Effect.SystemProcess: pid :: forall r_aixn. Member SystemProcess r_aixn => Sem r_aixn Pid
- Polysemy.Process.Effect.SystemProcess: readStderr :: forall r_ai0V. Member SystemProcess r_ai0V => Sem r_ai0V ByteString
+ Polysemy.Process.Effect.SystemProcess: readStderr :: forall r_aixk. Member SystemProcess r_aixk => Sem r_aixk ByteString
- Polysemy.Process.Effect.SystemProcess: readStdout :: forall r_ai0U. Member SystemProcess r_ai0U => Sem r_ai0U ByteString
+ Polysemy.Process.Effect.SystemProcess: readStdout :: forall r_aixj. Member SystemProcess r_aixj => Sem r_aixj ByteString
- Polysemy.Process.Effect.SystemProcess: signal :: forall r_ai0Z. Member SystemProcess r_ai0Z => Signal -> Sem r_ai0Z ()
+ Polysemy.Process.Effect.SystemProcess: signal :: forall r_aixo. Member SystemProcess r_aixo => Signal -> Sem r_aixo ()
- Polysemy.Process.Effect.SystemProcess: wait :: forall r_ai11. Member SystemProcess r_ai11 => Sem r_ai11 ExitCode
+ Polysemy.Process.Effect.SystemProcess: wait :: forall r_aixq. Member SystemProcess r_aixq => Sem r_aixq ExitCode
- Polysemy.Process.Effect.SystemProcess: writeStdin :: forall r_ai0W. Member SystemProcess r_ai0W => ByteString -> Sem r_ai0W ()
+ Polysemy.Process.Effect.SystemProcess: writeStdin :: forall r_aixl. Member SystemProcess r_aixl => ByteString -> Sem r_aixl ()
- Polysemy.Process.ProcessOutput: chunk :: forall p_adza a_adzb r_adBu. Member (ProcessOutput p_adza a_adzb) r_adBu => ByteString -> ByteString -> Sem r_adBu ([a_adzb], ByteString)
+ Polysemy.Process.ProcessOutput: chunk :: forall p_ae2W a_ae2X r_ae5g. Member (ProcessOutput p_ae2W a_ae2X) r_ae5g => ByteString -> ByteString -> Sem r_ae5g ([a_ae2X], ByteString)
- Polysemy.Process.Pty: handle :: forall r_agXj. Member Pty r_agXj => Sem r_agXj Handle
+ Polysemy.Process.Pty: handle :: forall r_ahtI. Member Pty r_ahtI => Sem r_ahtI Handle
- Polysemy.Process.Pty: resize :: forall r_agXk. Member Pty r_agXk => Rows -> Cols -> Sem r_agXk ()
+ Polysemy.Process.Pty: resize :: forall r_ahtJ. Member Pty r_ahtJ => Rows -> Cols -> Sem r_ahtJ ()
- Polysemy.Process.Pty: size :: forall r_agXn. Member Pty r_agXn => Sem r_agXn (Rows, Cols)
+ Polysemy.Process.Pty: size :: forall r_ahtM. Member Pty r_ahtM => Sem r_ahtM (Rows, Cols)
- Polysemy.Process.SystemProcess: pid :: forall r_ai0Y. Member SystemProcess r_ai0Y => Sem r_ai0Y Pid
+ Polysemy.Process.SystemProcess: pid :: forall r_aixn. Member SystemProcess r_aixn => Sem r_aixn Pid
- Polysemy.Process.SystemProcess: readStderr :: forall r_ai0V. Member SystemProcess r_ai0V => Sem r_ai0V ByteString
+ Polysemy.Process.SystemProcess: readStderr :: forall r_aixk. Member SystemProcess r_aixk => Sem r_aixk ByteString
- Polysemy.Process.SystemProcess: readStdout :: forall r_ai0U. Member SystemProcess r_ai0U => Sem r_ai0U ByteString
+ Polysemy.Process.SystemProcess: readStdout :: forall r_aixj. Member SystemProcess r_aixj => Sem r_aixj ByteString
- Polysemy.Process.SystemProcess: signal :: forall r_ai0Z. Member SystemProcess r_ai0Z => Signal -> Sem r_ai0Z ()
+ Polysemy.Process.SystemProcess: signal :: forall r_aixo. Member SystemProcess r_aixo => Signal -> Sem r_aixo ()
- Polysemy.Process.SystemProcess: wait :: forall r_ai11. Member SystemProcess r_ai11 => Sem r_ai11 ExitCode
+ Polysemy.Process.SystemProcess: wait :: forall r_aixq. Member SystemProcess r_aixq => Sem r_aixq ExitCode
- Polysemy.Process.SystemProcess: writeStdin :: forall r_ai0W. Member SystemProcess r_ai0W => ByteString -> Sem r_ai0W ()
+ Polysemy.Process.SystemProcess: writeStdin :: forall r_aixl. Member SystemProcess r_aixl => ByteString -> Sem r_aixl ()
Files
- changelog.md +6/−0
- lib/Polysemy/Process.hs +15/−1
- lib/Polysemy/Process/Data/Pid.hs +2/−2
- lib/Polysemy/Process/Data/ProcessError.hs +5/−5
- lib/Polysemy/Process/Data/ProcessKill.hs +5/−5
- lib/Polysemy/Process/Data/ProcessOptions.hs +5/−5
- lib/Polysemy/Process/Data/ProcessOutputParseResult.hs +2/−2
- lib/Polysemy/Process/Data/PtyError.hs +2/−2
- lib/Polysemy/Process/Data/PtyResources.hs +2/−2
- lib/Polysemy/Process/Data/SystemProcessError.hs +6/−6
- lib/Polysemy/Process/Effect/Interrupt.hs +44/−0
- lib/Polysemy/Process/Effect/Process.hs +10/−10
- lib/Polysemy/Process/Effect/ProcessInput.hs +4/−4
- lib/Polysemy/Process/Effect/ProcessOutput.hs +8/−8
- lib/Polysemy/Process/Effect/Pty.hs +8/−8
- lib/Polysemy/Process/Effect/SystemProcess.hs +13/−13
- lib/Polysemy/Process/Executable.hs +4/−4
- lib/Polysemy/Process/Interpreter/Interrupt.hs +244/−0
- lib/Polysemy/Process/Interpreter/Process.hs +14/−14
- lib/Polysemy/Process/Interpreter/ProcessIO.hs +6/−6
- lib/Polysemy/Process/Interpreter/ProcessInput.hs +3/−3
- lib/Polysemy/Process/Interpreter/ProcessOneshot.hs +5/−5
- lib/Polysemy/Process/Interpreter/ProcessOutput.hs +10/−10
- lib/Polysemy/Process/Interpreter/Pty.hs +2/−2
- lib/Polysemy/Process/Interpreter/SystemProcess.hs +11/−11
- lib/Polysemy/Process/ProcessOutput.hs +1/−1
- lib/Polysemy/Process/Pty.hs +1/−1
- lib/Polysemy/Process/SysProcConf.hs +4/−4
- lib/Polysemy/Process/SystemProcess.hs +2/−2
- polysemy-process.cabal +34/−26
- readme.md +1/−1
- test/Main.hs +6/−1
- test/Polysemy/Process/Test/InterruptTest.hs +31/−0
changelog.md view
@@ -1,3 +1,9 @@+# 0.14.0.0++## Breaking++* Move `Interrupt` here from `polysemy-conc`.+ # 0.10.0.0 * Add `oneshot` variants of `Process` interpreters that send `Stop` to the individual actions inside the scope, modeling
lib/Polysemy/Process.hs view
@@ -1,4 +1,4 @@--- |Description: Polysemy Effects for System Processes+-- | Description: Polysemy Effects for System Processes module Polysemy.Process ( -- * Introduction -- $intro@@ -35,6 +35,10 @@ Pty, withPty, + -- ** Signal Handling+ -- $signal+ Interrupt,+ -- * Interpreters -- ** Process interpretProcess,@@ -92,6 +96,11 @@ -- ** Pty interpretPty, + -- ** Signal Handling+ interpretInterrupt,+ interpretInterruptOnce,+ interpretInterruptNull,+ -- * Tools resolveExecutable, ) where@@ -104,6 +113,7 @@ import Polysemy.Process.Data.ProcessOptions (ProcessOptions (ProcessOptions)) import Polysemy.Process.Data.ProcessOutputParseResult (ProcessOutputParseResult (..)) import Polysemy.Process.Data.SystemProcessError (SystemProcessError, SystemProcessScopeError)+import Polysemy.Process.Effect.Interrupt (Interrupt) import Polysemy.Process.Effect.Process ( Process (..), recv,@@ -118,6 +128,7 @@ import Polysemy.Process.Effect.Pty (Pty, withPty) import Polysemy.Process.Effect.SystemProcess (SystemProcess, withSystemProcess, withSystemProcess_) import Polysemy.Process.Executable (resolveExecutable)+import Polysemy.Process.Interpreter.Interrupt (interpretInterrupt, interpretInterruptNull, interpretInterruptOnce) import Polysemy.Process.Interpreter.Process ( interpretInputHandle, interpretInputHandleBuffered,@@ -185,3 +196,6 @@ -- -- The effect 'Pty' abstracts pseudo terminals. -- See "Polysemy.Process.Pty" for its constructors.++-- $signal+-- #signal#
lib/Polysemy/Process/Data/Pid.hs view
@@ -1,7 +1,7 @@--- |Pid data type, Internal+-- | Pid data type, Internal module Polysemy.Process.Data.Pid where --- |A process ID.+-- | A process ID. newtype Pid = Pid { unPid :: Int } deriving stock (Eq, Show, Read)
lib/Polysemy/Process/Data/ProcessError.hs view
@@ -1,20 +1,20 @@ {-# options_haddock prune #-} --- |Description: ProcessError, Internal+-- | Description: ProcessError, Internal module Polysemy.Process.Data.ProcessError where import System.Exit (ExitCode) import Polysemy.Process.Data.SystemProcessError (SystemProcessScopeError) --- |Signal error for 'Polysemy.Process.Process'.+-- | Signal error for 'Polysemy.Process.Process'. data ProcessError =- -- |Something broke.+ -- | Something broke. Unknown Text |- -- |The process failed to start.+ -- | The process failed to start. StartFailed SystemProcessScopeError |- -- |The process terminated with exit code.+ -- | The process terminated with exit code. Exit ExitCode deriving stock (Eq, Show)
lib/Polysemy/Process/Data/ProcessKill.hs view
@@ -1,16 +1,16 @@--- |Data type indicating whether to kill a process after exiting its scope.+-- | Data type indicating whether to kill a process after exiting its scope. module Polysemy.Process.Data.ProcessKill where import Polysemy.Time (NanoSeconds) --- |Indicate whether to kill a process after exiting the scope in which it was used, if it hasn't terminated.+-- | Indicate whether to kill a process after exiting the scope in which it was used, if it hasn't terminated. data ProcessKill =- -- |Wait for the specified interval, then kill.+ -- | Wait for the specified interval, then kill. KillAfter NanoSeconds |- -- |Kill immediately.+ -- | Kill immediately. KillImmediately |- -- |Wait indefinitely for the process to terminate.+ -- | Wait indefinitely for the process to terminate. KillNever deriving stock (Eq, Show)
lib/Polysemy/Process/Data/ProcessOptions.hs view
@@ -1,17 +1,17 @@--- |Data type containing options for processes.+-- | Data type containing options for processes. module Polysemy.Process.Data.ProcessOptions where import qualified Polysemy.Process.Data.ProcessKill as ProcessKill import Polysemy.Process.Data.ProcessKill (ProcessKill) --- |Controls the behaviour of 'Polysemy.Process.Process' interpreters.+-- | Controls the behaviour of 'Polysemy.Process.Process' interpreters. data ProcessOptions = ProcessOptions {- -- |Whether to discard output chunks if the queue is full.+ -- | Whether to discard output chunks if the queue is full. discard :: Bool,- -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.+ -- | Maximum number of chunks allowed to be queued for each of the three standard pipes. qsize :: Int,- -- |What to do if the process hasn't terminated when exiting the scope.+ -- | What to do if the process hasn't terminated when exiting the scope. kill :: ProcessKill } deriving stock (Eq, Show)
lib/Polysemy/Process/Data/ProcessOutputParseResult.hs view
@@ -1,7 +1,7 @@--- |A data type encoding the result of an incremental parser for process output.+-- | A data type encoding the result of an incremental parser for process output. module Polysemy.Process.Data.ProcessOutputParseResult where --- |An incremental parse result, potentially a partial result containing a continuation function.+-- | An incremental parse result, potentially a partial result containing a continuation function. data ProcessOutputParseResult a = Done { value :: a, leftover :: ByteString } |
lib/Polysemy/Process/Data/PtyError.hs view
@@ -1,9 +1,9 @@ {-# options_haddock prune #-} --- |PtyError ADT, Internal+-- | PtyError ADT, Internal module Polysemy.Process.Data.PtyError where --- |Internal error used by an interpreter for 'Polysemy.Process.Pty'.+-- | Internal error used by an interpreter for 'Polysemy.Process.Pty'. data PtyError = PtyError Text deriving stock (Eq, Show)
lib/Polysemy/Process/Data/PtyResources.hs view
@@ -1,13 +1,13 @@ {-# options_haddock prune #-} --- |Description: PtyResources ADT, Internal+-- | Description: PtyResources ADT, Internal module Polysemy.Process.Data.PtyResources where import System.IO (Handle) import System.Posix (Fd) import System.Posix.Pty (Pty) --- |The resources used by the default interpreter for 'Polysemy.Process.Pty'.+-- | The resources used by the default interpreter for 'Polysemy.Process.Pty'. data PtyResources = PtyResources { primary :: Fd,
lib/Polysemy/Process/Data/SystemProcessError.hs view
@@ -1,19 +1,19 @@ {-# options_haddock prune #-} --- |Description: SystemProcessError, Internal+-- | Description: SystemProcessError, Internal module Polysemy.Process.Data.SystemProcessError where --- |Error for 'Polysemy.Process.SystemProcess'.+-- | Error for 'Polysemy.Process.SystemProcess'. data SystemProcessError =- -- |The process terminated.+ -- | The process terminated. Terminated Text |- -- |Stdio was requested, but the process was started without pipes.+ -- | Stdio was requested, but the process was started without pipes. NoPipes deriving stock (Eq, Show) --- |Error for the scope of 'Polysemy.Process.SystemProcess'.+-- | Error for the scope of 'Polysemy.Process.SystemProcess'. data SystemProcessScopeError =- -- |The process couldn't start.+ -- | The process couldn't start. StartFailed Text deriving stock (Eq, Show)
+ lib/Polysemy/Process/Effect/Interrupt.hs view
@@ -0,0 +1,44 @@+{-# options_haddock prune #-}+-- | Description: Interrupt effect+module Polysemy.Process.Effect.Interrupt where++-- | The interrupt handler effect allows three kinds of interaction for interrupt signals:+--+-- - Execute a callback when a signal is received+-- - Block a thread until a signal is received+-- - Kill a thread when a signal is received+--+-- For documentation on the constructors, see the module "Polysemy.Process.Effect.Interrupt".+--+-- @+-- import qualified Polysemy.Process.Effect.Interrupt as Interrupt+--+-- prog = do+-- Interrupt.register "task 1" (putStrLn "interrupted")+-- Interrupt.killOnQuit $ forever do+-- doSomeWork+-- @+data Interrupt :: Effect where+ -- | Add a computation to be executed on interrupt, using the first argument as a key.+ Register :: Text -> IO () -> Interrupt m ()+ -- | Remove the previously added handler with the given key.+ Unregister :: Text -> Interrupt m ()+ -- | Manually trigger the interrupt.+ Quit :: Interrupt m ()+ -- | Block until an interrupt is triggered.+ WaitQuit :: Interrupt m ()+ -- | Indicate whether an interrupt was triggered.+ Interrupted :: Interrupt m Bool+ -- | Execute a computation, waiting for it to finish, killing its thread on interrupt.+ KillOnQuit :: Text -> m a -> Interrupt m (Maybe a)++makeSem ''Interrupt++-- | Variant of 'killOnQuit' that returns @()@.+killOnQuit_ ::+ Member Interrupt r =>+ Text ->+ Sem r a ->+ Sem r ()+killOnQuit_ desc ma =+ void (killOnQuit desc ma)
lib/Polysemy/Process/Effect/Process.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |Description: Process Effect, Internal+-- | Description: Process Effect, Internal module Polysemy.Process.Effect.Process where import Polysemy.Input (Input (Input))@@ -8,7 +8,7 @@ import Polysemy.Resume (interpretResumable, restop, type (!!)) import Prelude hiding (send) --- |Abstraction of a process with input and output.+-- | Abstraction of a process with input and output. -- -- This effect is intended to be used in a scoped_ manner: --@@ -36,20 +36,20 @@ makeSem_ ''Process --- |Obtain a chunk of output.+-- | Obtain a chunk of output. recv :: ∀ i o r . Member (Process i o) r => Sem r o --- |Send data to stdin.+-- | Send data to stdin. send :: ∀ i o r . Member (Process i o) r => i -> Sem r () --- |Create a scoped_ resource for 'Process'.+-- | Create a scoped_ resource for 'Process'. -- The process configuration may depend on the provided value of type @param@. -- This variant models daemon processes that are expected to run forever, with 'Polysemy.Resume.Stop' being sent to this -- function, if at all.@@ -61,7 +61,7 @@ withProcess = scoped @param --- |Create a scoped_ resource for 'Process'.+-- | Create a scoped_ resource for 'Process'. -- The process configuration may depend on the provided value of type @param@. -- This variant models processes that are expected to terminate, with 'Polysemy.Resume.Stop' being sent to individual -- actions within the scope.@@ -73,7 +73,7 @@ withProcessOneshot = scoped @param --- |Create a scoped_ resource for 'Process'.+-- | Create a scoped_ resource for 'Process'. -- The process configuration is provided to the interpreter statically. -- This variant models daemon processes that are expected to run forever, with 'Polysemy.Resume.Stop' being sent to this -- function, if at all.@@ -84,7 +84,7 @@ withProcess_ = scoped_ --- |Create a scoped_ resource for 'Process'.+-- | Create a scoped_ resource for 'Process'. -- The process configuration is provided to the interpreter statically. -- This variant models processes that are expected to terminate, with 'Polysemy.Resume.Stop' being sent to individual -- actions within the scope.@@ -95,7 +95,7 @@ withProcessOneshot_ = scoped_ --- |Convert 'Output' and 'Input' to 'Process' for a daemon process.+-- | Convert 'Output' and 'Input' to 'Process' for a daemon process. runProcessIO :: ∀ i o r . Member (Process i o) r =>@@ -109,7 +109,7 @@ Output o -> send @i @o o --- |Convert 'Output' and 'Input' to 'Process' for a oneshot process.+-- | Convert 'Output' and 'Input' to 'Process' for a oneshot process. runProcessOneshotIO :: ∀ i o err r . Member (Process i o !! err) r =>
lib/Polysemy/Process/Effect/ProcessInput.hs view
@@ -1,14 +1,14 @@ {-# options_haddock prune #-} --- |Description: ProcessInput effect, Internal.+-- | Description: ProcessInput effect, Internal. module Polysemy.Process.Effect.ProcessInput where --- |This effect is used by the effect 'Polysemy.Process.Process' to encode values for process input.+-- | This effect is used by the effect 'Polysemy.Process.Process' to encode values for process input. -- example using a parser. data ProcessInput a :: Effect where- -- |Encode a value for enqueueing it to a process' stdin.+ -- | Encode a value for enqueueing it to a process' stdin. Encode ::- -- |The value to encode.+ -- | The value to encode. a -> ProcessInput a m ByteString
lib/Polysemy/Process/Effect/ProcessOutput.hs view
@@ -1,28 +1,28 @@ {-# options_haddock prune #-} --- |Description: ProcessOutput effect, Internal.+-- | Description: ProcessOutput effect, Internal. module Polysemy.Process.Effect.ProcessOutput where --- |Kind tag for selecting the 'ProcessOutput' handler for stdout/stderr.+-- | Kind tag for selecting the 'ProcessOutput' handler for stdout/stderr. data OutputPipe =- -- |Tag for stdout.+ -- | Tag for stdout. Stdout |- -- |Tag for stderr.+ -- | Tag for stderr. Stderr deriving stock (Eq, Show) --- |This effect is used by the effect 'Polysemy.Process.Process' to accumulate and decode chunks of 'ByteString's, for+-- | This effect is used by the effect 'Polysemy.Process.Process' to accumulate and decode chunks of 'ByteString's, for -- example using a parser. -- The interpreter may be stateful or stateless, since the constructor 'Chunk' is expected to be called with both the -- accumulated unprocessed output as well as the new chunk. data ProcessOutput (p :: OutputPipe) a :: Effect where- -- |Add a chunk of output to the accumulator, returning any number of successfully parsed values and the leftover+ -- | Add a chunk of output to the accumulator, returning any number of successfully parsed values and the leftover -- output. Chunk ::- -- |The accumulation of the previous leftovers.+ -- | The accumulation of the previous leftovers. ByteString ->- -- |The new chunk read from the process.+ -- | The new chunk read from the process. ByteString -> ProcessOutput p a m ([a], ByteString)
lib/Polysemy/Process/Effect/Pty.hs view
@@ -1,34 +1,34 @@ {-# options_haddock prune #-} --- |Description: Pty Effect, Internal+-- | Description: Pty Effect, Internal module Polysemy.Process.Effect.Pty where import System.IO (Handle) --- |Horizontal size of a pseudo terminal in characters.+-- | Horizontal size of a pseudo terminal in characters. newtype Rows = Rows { unRows :: Int } deriving stock (Eq, Show, Generic) deriving newtype (Num, Real, Enum, Integral, Ord) --- |Vertical size of a pseudo terminal in characters.+-- | Vertical size of a pseudo terminal in characters. newtype Cols = Cols { unCols :: Int } deriving stock (Eq, Show, Generic) deriving newtype (Num, Real, Enum, Integral, Ord) --- |A pseudo terminal, to be scoped with 'withPty'.+-- | A pseudo terminal, to be scoped with 'withPty'. data Pty :: Effect where- -- |The file descriptor that can be connected to stdio of a process.+ -- | The file descriptor that can be connected to stdio of a process. Handle :: Pty m Handle- -- |Set the size of the terminal.+ -- | Set the size of the terminal. Resize :: Rows -> Cols -> Pty m ()- -- |Get the size of the terminal.+ -- | Get the size of the terminal. Size :: Pty m (Rows, Cols) makeSem ''Pty --- |Bracket an action with the creation and destruction of a pseudo terminal.+-- | Bracket an action with the creation and destruction of a pseudo terminal. withPty :: Member (Scoped_ Pty) r => InterpreterFor Pty r
lib/Polysemy/Process/Effect/SystemProcess.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |Description: SystemProcess Effect, Internal+-- | Description: SystemProcess Effect, Internal module Polysemy.Process.Effect.SystemProcess where import Polysemy.Resume (type (!!))@@ -10,25 +10,25 @@ import Polysemy.Process.Data.Pid (Pid) --- |Low-level interface for a process, operating on raw chunks of bytes.+-- | Low-level interface for a process, operating on raw chunks of bytes. -- Interface is modeled after "System.Process". data SystemProcess :: Effect where- -- |Read a chunk from stdout.+ -- | Read a chunk from stdout. ReadStdout :: SystemProcess m ByteString- -- |Read a chunk from stderr.+ -- | Read a chunk from stderr. ReadStderr :: SystemProcess m ByteString- -- |Write a 'ByteString' to stdin.+ -- | Write a 'ByteString' to stdin. WriteStdin :: ByteString -> SystemProcess m ()- -- |Obtain the process ID.+ -- | Obtain the process ID. Pid :: SystemProcess m Pid- -- |Send a 'System.Posix.Signal' to the process.+ -- | Send a 'System.Posix.Signal' to the process. Signal :: Signal -> SystemProcess m ()- -- |Wait for the process to terminate, returning its exit code.+ -- | Wait for the process to terminate, returning its exit code. Wait :: SystemProcess m ExitCode makeSem ''SystemProcess --- |Create a scoped resource for 'SystemProcess'.+-- | Create a scoped resource for 'SystemProcess'. -- The process configuration may depend on the provided value of type @param@. withSystemProcess :: ∀ param err r .@@ -38,7 +38,7 @@ withSystemProcess = scoped @param --- |Create a scoped resource for 'SystemProcess'.+-- | Create a scoped resource for 'SystemProcess'. -- The process configuration is provided to the interpreter statically. withSystemProcess_ :: ∀ err r .@@ -47,21 +47,21 @@ withSystemProcess_ = scoped_ --- |Send signal INT(2) to the process.+-- | Send signal INT(2) to the process. interrupt :: Member SystemProcess r => Sem r () interrupt = signal Signal.sigINT --- |Send signal TERM(15) to the process.+-- | Send signal TERM(15) to the process. term :: Member SystemProcess r => Sem r () term = signal Signal.sigTERM --- |Send signal KILL(9) to the process.+-- | Send signal KILL(9) to the process. kill :: Member SystemProcess r => Sem r ()
lib/Polysemy/Process/Executable.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |Description: Executable helpers, Internal+-- | Description: Executable helpers, Internal module Polysemy.Process.Executable where import Path (Abs, File, Path, Rel, toFilePath)@@ -26,12 +26,12 @@ pathText = toText (toFilePath path) --- |Find a file in @$PATH@, verifying that it is executable by this process.+-- | Find a file in @$PATH@, verifying that it is executable by this process. resolveExecutable :: Member (Embed IO) r =>- -- |Executable name, for @$PATH@ lookup and error messages+ -- | Executable name, for @$PATH@ lookup and error messages Path Rel File ->- -- |Explicit override to be checked for adequate permissions+ -- | Explicit override to be checked for adequate permissions Maybe (Path Abs File) -> Sem r (Either Text (Path Abs File)) resolveExecutable exe = \case
+ lib/Polysemy/Process/Interpreter/Interrupt.hs view
@@ -0,0 +1,244 @@+{-# options_haddock prune #-}+{-# language FieldSelectors #-}++-- | Description: Interrupt interpreters+module Polysemy.Process.Interpreter.Interrupt where++import qualified Control.Concurrent.Async as A+import Control.Concurrent.Async (AsyncCancelled)+import Control.Concurrent.STM (TVar, newTVarIO)+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import qualified Data.Text.IO as Text+import qualified Polysemy.Conc.Effect.Critical as Critical+import Polysemy.Conc.Effect.Critical (Critical)+import Polysemy.Conc.Effect.Race (Race)+import qualified Polysemy.Conc.Effect.Sync as Sync+import Polysemy.Conc.Interpreter.Sync (interpretSync)+import Polysemy.Conc.Race (race_)+import Polysemy.Internal.Tactics (liftT)+import Polysemy.Time (Seconds (Seconds))+import System.IO (stderr)+import System.Posix.Signals (+ Handler (Catch, CatchInfo, CatchInfoOnce, CatchOnce),+ SignalInfo,+ installHandler,+ keyboardSignal,+ )++import Polysemy.Process.Effect.Interrupt (Interrupt (..))++putErr ::+ Member (Embed IO) r =>+ Text ->+ Sem r ()+putErr =+ embed . Text.hPutStrLn stderr++data InterruptState =+ InterruptState {+ quit :: !(MVar ()),+ finished :: !(MVar ()),+ listeners :: !(Set Text),+ original :: !(SignalInfo -> IO ()),+ handlers :: !(Map Text (IO ()))+ }++modListeners :: (Set Text -> Set Text) -> InterruptState -> InterruptState+modListeners f s@InterruptState {listeners} =+ s {listeners = f listeners}++modHandlers :: (Map Text (IO ()) -> Map Text (IO ())) -> InterruptState -> InterruptState+modHandlers f s@InterruptState {handlers} =+ s {handlers = f handlers}++waitQuit ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Sem r ()+waitQuit = do+ mv <- atomicGets quit+ embed (readMVar mv)++checkListeners ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Sem r ()+checkListeners =+ whenM (atomicGets (Set.null . listeners)) do+ fin <- atomicGets finished+ void (embed (tryPutMVar fin ()))++onQuit ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Text ->+ Sem r a ->+ Sem r a+onQuit name ma = do+ atomicModify' (modListeners (Set.insert name))+ waitQuit+ a <- ma+ atomicModify' (modListeners (Set.delete name))+ checkListeners+ pure a++processHandler ::+ Member (Embed IO) r =>+ Text ->+ IO () ->+ Sem r ()+processHandler name thunk = do+ putErr ("processing interrupt handler: " <> name)+ embed thunk++execInterrupt ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Sem r (SignalInfo -> Sem r ())+execInterrupt = do+ InterruptState quitSignal finishSignal _ orig _ <- atomicGet+ whenM (embed (tryPutMVar quitSignal ())) do+ traverse_ (uncurry processHandler) . Map.toList =<< atomicGets handlers+ checkListeners+ embed (takeMVar finishSignal)+ embed . orig <$ putErr "interrupt handlers finished"++registerHandler ::+ Member (AtomicState InterruptState) r =>+ Text ->+ IO () ->+ Sem r ()+registerHandler name handler =+ atomicModify' (modHandlers (Map.insert name handler))++awaitOrKill ::+ Members [AtomicState InterruptState, Critical, Race, Async, Embed IO] r =>+ Text ->+ A.Async (Maybe a) ->+ Sem r (Maybe a)+awaitOrKill desc handle = do+ interpretSync @() do+ race_ (catchCritical (await handle)) kill+ where+ catchCritical =+ maybe waitKill (pure . Just) <=< Critical.catchAs @AsyncCancelled Nothing+ waitKill =+ Nothing <$ Sync.wait @() (Seconds 1)+ kill = do+ onQuit desc do+ putErr ("killing " <> desc)+ cancel handle+ putErr ("killed " <> desc)+ Sync.putBlock ()+ pure Nothing++interpretInterruptState ::+ Members [AtomicState InterruptState, Critical, Race, Async, Embed IO] r =>+ InterpreterFor Interrupt r+interpretInterruptState =+ interpretH \case+ Register name handler ->+ liftT (registerHandler name handler)+ Unregister name ->+ liftT $ atomicModify' \ s@InterruptState {handlers} -> s {handlers = Map.delete name handlers}+ WaitQuit ->+ liftT waitQuit+ Quit ->+ liftT do+ putErr "manual interrupt"+ void execInterrupt+ Interrupted ->+ liftT . fmap isJust . embed . tryReadMVar =<< atomicGets quit+ KillOnQuit desc ma -> do+ maT <- runT ma+ ins <- getInspectorT+ handle <- raise (interpretInterruptState (async maT))+ result <- liftT (awaitOrKill desc handle)+ pure (join . fmap (inspect ins) <$> result)+{-# inline interpretInterruptState #-}++broadcastInterrupt ::+ Members [AtomicState InterruptState, Embed IO] r =>+ SignalInfo ->+ Sem r ()+broadcastInterrupt sig = do+ putErr "caught interrupt signal"+ orig <- execInterrupt+ orig sig++-- The original handler is either the default handler that kills all threads or a handler installed by an environment+-- like ghcid.+-- In the latter case, not calling it results in ghcid misbehaving.+-- To distinguish the two cases, the constructor used by the default is 'Catch', while a custom handler should usually+-- use 'CatchOnce', since you don't want to catch repeated occurences of SIGINT, as it will surely cause problems.+originalHandler :: Handler -> (SignalInfo -> IO ())+originalHandler (CatchOnce thunk) =+ (const thunk)+originalHandler (CatchInfoOnce thunk) =+ thunk+originalHandler (Catch thunk) =+ (const thunk)+originalHandler (CatchInfo thunk) =+ thunk+originalHandler _ =+ const unit+{-# inline originalHandler #-}++installSignalHandler ::+ TVar InterruptState ->+ ((SignalInfo -> IO ()) -> Handler) ->+ IO Handler+installSignalHandler state consHandler =+ installHandler keyboardSignal (consHandler handler) Nothing+ where+ handler sig =+ runFinal $ embedToFinal @IO $ runAtomicStateTVar state (broadcastInterrupt sig)++-- | Interpret 'Interrupt' by installing a signal handler.+--+-- Takes a constructor for 'Handler'.+interpretInterruptWith ::+ Members [Critical, Race, Async, Embed IO] r =>+ ((SignalInfo -> IO ()) -> Handler) ->+ InterpreterFor Interrupt r+interpretInterruptWith consHandler sem = do+ quitMVar <- embed newEmptyMVar+ finishMVar <- embed newEmptyMVar+ state <- embed (newTVarIO (InterruptState quitMVar finishMVar Set.empty (const unit) Map.empty))+ orig <- embed $ installSignalHandler state consHandler+ runAtomicStateTVar state do+ atomicModify' \ s -> s {original = originalHandler orig}+ interpretInterruptState $ raiseUnder sem++-- | Interpret 'Interrupt' by installing a signal handler.+--+-- Catches repeat invocations of SIGINT.+interpretInterrupt ::+ Members [Critical, Race, Async, Embed IO] r =>+ InterpreterFor Interrupt r+interpretInterrupt =+ interpretInterruptWith CatchInfo++-- | Interpret 'Interrupt' by installing a signal handler.+--+-- Catches only the first invocation of SIGINT.+interpretInterruptOnce ::+ Members [Critical, Race, Async, Embed IO] r =>+ InterpreterFor Interrupt r+interpretInterruptOnce =+ interpretInterruptWith CatchInfoOnce++-- | Eliminate 'Interrupt' without interpreting.+interpretInterruptNull ::+ InterpreterFor Interrupt r+interpretInterruptNull =+ interpretH \case+ Register _ _ ->+ pureT ()+ Unregister _ ->+ pureT ()+ WaitQuit ->+ pureT ()+ Quit ->+ pureT ()+ Interrupted ->+ pureT False+ KillOnQuit _ _ ->+ pureT Nothing
lib/Polysemy/Process/Interpreter/Process.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |Description: Process Interpreters, Internal+-- | Description: Process Interpreters, Internal module Polysemy.Process.Interpreter.Process where import Control.Concurrent.STM.TBMQueue (TBMQueue)@@ -132,7 +132,7 @@ withQueues qSize action = withSTMResources qSize \ qs -> interpretQueues qs action --- |Call a chunk reading action repeatedly, pass the bytes to 'ProcessOutput' and enqueue its results.+-- | Call a chunk reading action repeatedly, pass the bytes to 'ProcessOutput' and enqueue its results. -- As soon as an empty chunk is encountered, the queue is closed if the gating action returns 'False'. -- The conditional closing is for the purpose of keeping the queue alive until the last producer has written all -- received chunks – this is important when both stdout and stderr are written to the same queue.@@ -242,7 +242,7 @@ raiseUnder $ queues @err options sem --- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,+-- | Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's, -- deferring decoding of stdout and stderr to the interpreters of two 'ProcessOutput' effects. -- This variant: -- - Models a daemon process that is not expected to terminate, causing 'Stop' to be sent to the scope callsite instead@@ -270,7 +270,7 @@ mapStop ProcessError.StartFailed do pscope @SystemProcessScopeError options (raise . raiseUnder . proc) p (insertAt @4 sem) --- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,+-- | Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's, -- deferring decoding of stdout and stderr to the interpreters of two 'ProcessOutput' effects. -- This variant: -- - Models a daemon process that is not expected to terminate, causing 'Stop' to be sent to the scope callsite instead@@ -293,7 +293,7 @@ mapStop ProcessError.StartFailed do scope @SystemProcessScopeError options (insertAt @4 sem) --- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'.+-- | Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'. -- This variant: -- - Models a daemon process that is not expected to terminate, causing 'Stop' to be sent to the scope callsite instead -- of individual 'Process' actions.@@ -312,7 +312,7 @@ interpretProcess options (insertAt @0 . proc) . raiseUnder --- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'.+-- | Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'. -- This variant: -- - Models a daemon process that is not expected to terminate, causing 'Stop' to be sent to the scope callsite instead -- of individual 'Process' actions.@@ -329,7 +329,7 @@ interpretProcess_ options . raiseUnder --- |Reinterpret 'Input' and 'Output' as 'Process'.+-- | Reinterpret 'Input' and 'Output' as 'Process'. interpretInputOutputProcess :: ∀ i o r . Member (Process i o) r =>@@ -338,7 +338,7 @@ runOutputSem (Process.send @i @o) . runInputSem (Process.recv @i @o) --- |Interpret 'Input ByteString' by polling a 'Handle' and stopping with 'ProcessError' when it fails.+-- | Interpret 'Input ByteString' by polling a 'Handle' and stopping with 'ProcessError' when it fails. interpretInputHandleBuffered :: Member (Embed IO) r => Handle ->@@ -348,7 +348,7 @@ Input -> stopNote (Unknown "handle closed") =<< tryMaybe (hGetSome handle 4096) --- |Interpret 'Input ByteString' by polling a 'Handle' and stopping with 'ProcessError' when it fails.+-- | Interpret 'Input ByteString' by polling a 'Handle' and stopping with 'ProcessError' when it fails. -- This variant deactivates buffering for the 'Handle'. interpretInputHandle :: Member (Embed IO) r =>@@ -358,7 +358,7 @@ tryAny_ (hSetBuffering handle NoBuffering) interpretInputHandleBuffered handle sem --- |Interpret 'Output ByteString' by writing to a 'Handle' and stopping with 'ProcessError' when it fails.+-- | Interpret 'Output ByteString' by writing to a 'Handle' and stopping with 'ProcessError' when it fails. interpretOutputHandleBuffered :: Member (Embed IO) r => Handle ->@@ -368,7 +368,7 @@ Output o -> stopNote (Unknown "handle closed") =<< tryMaybe (hPut handle o) --- |Interpret 'Output ByteString' by writing to a 'Handle' and stopping with 'ProcessError' when it fails.+-- | Interpret 'Output ByteString' by writing to a 'Handle' and stopping with 'ProcessError' when it fails. -- This variant deactivates buffering for the 'Handle'. interpretOutputHandle :: Member (Embed IO) r =>@@ -378,7 +378,7 @@ tryAny_ (hSetBuffering handle NoBuffering) interpretOutputHandleBuffered handle sem --- |Interpret 'Process' in terms of 'Input' and 'Output'.+-- | Interpret 'Process' in terms of 'Input' and 'Output'. -- Since the @i@ and @o@ parameters correspond to the abstraction of stdio fds of an external system process, @i@ is -- written by 'Output' and @o@ is read from 'Input'. -- This is useful to abstract the current process's stdio as an external process, with input and output swapped.@@ -395,7 +395,7 @@ interpretResumable (handleProcessWithQueues (stop . Unknown)) . raiseUnder2 --- |Interpret 'Process' in terms of two 'Handle's.+-- | Interpret 'Process' in terms of two 'Handle's. -- This is useful to abstract the current process's stdio as an external process, with input and output swapped. -- The first 'Handle' argument corresponds to the @o@ parameter, the second one to @i@, despite the first one usually -- being the current process's stdin.@@ -414,7 +414,7 @@ interpretProcessIO @i @o @ProcessError @ProcessError options . raiseUnder2 --- |Interpret 'Process' using the current process's stdin and stdout.+-- | Interpret 'Process' using the current process's stdin and stdout. -- This mirrors the usual abstraction of an external process, to whose stdin would be /written/, while the current one's -- is /read/. interpretProcessCurrent ::
lib/Polysemy/Process/Interpreter/ProcessIO.hs view
@@ -1,4 +1,4 @@--- |Interpreters for 'ProcessOutput' and 'ProcessInput', Internal+-- | Interpreters for 'ProcessOutput' and 'ProcessInput', Internal module Polysemy.Process.Interpreter.ProcessIO where import Polysemy.Process.Effect.ProcessInput (ProcessInput)@@ -12,7 +12,7 @@ interpretProcessOutputTextLines, ) --- |The effects used by 'Polysemy.Process.Process' to send and receive chunks of bytes to and from a process.+-- | The effects used by 'Polysemy.Process.Process' to send and receive chunks of bytes to and from a process. type ProcessIO i o = [ ProcessInput i,@@ -20,7 +20,7 @@ ProcessOutput 'Stderr o ] --- |Interpret 'ProcessIO' with plain 'ByteString's without chunking.+-- | Interpret 'ProcessIO' with plain 'ByteString's without chunking. -- Silently discards stderr. interpretProcessByteString :: InterpretersFor (ProcessIO ByteString ByteString) r@@ -29,7 +29,7 @@ interpretProcessOutputId @'Stdout . interpretProcessInputId --- |Interpret 'ProcessIO' with 'ByteString's chunked as lines.+-- | Interpret 'ProcessIO' with 'ByteString's chunked as lines. -- Silently discards stderr. interpretProcessByteStringLines :: InterpretersFor (ProcessIO ByteString ByteString) r@@ -38,7 +38,7 @@ interpretProcessOutputLines @'Stdout . interpretProcessInputId --- |Interpret 'ProcessIO' with plain 'Text's without chunking.+-- | Interpret 'ProcessIO' with plain 'Text's without chunking. -- Silently discards stderr. interpretProcessText :: InterpretersFor (ProcessIO Text Text) r@@ -47,7 +47,7 @@ interpretProcessOutputText @'Stdout . interpretProcessInputText --- |Interpret 'ProcessIO' with 'Text's chunked as lines.+-- | Interpret 'ProcessIO' with 'Text's chunked as lines. -- Silently discards stderr. interpretProcessTextLines :: InterpretersFor (ProcessIO Text Text) r
lib/Polysemy/Process/Interpreter/ProcessInput.hs view
@@ -1,11 +1,11 @@ {-# options_haddock prune #-} --- |Description: ProcessInput Interpreters, Internal+-- | Description: ProcessInput Interpreters, Internal module Polysemy.Process.Interpreter.ProcessInput where import Polysemy.Process.Effect.ProcessInput (ProcessInput (Encode)) --- |Interpret 'ProcessInput' by passing 'ByteString' through.+-- | Interpret 'ProcessInput' by passing 'ByteString' through. interpretProcessInputId :: InterpreterFor (ProcessInput ByteString) r interpretProcessInputId = interpret \case@@ -13,7 +13,7 @@ pure value {-# inline interpretProcessInputId #-} --- |Interpret 'ProcessInput' by UTF-8-encoding 'Text'.+-- | Interpret 'ProcessInput' by UTF-8-encoding 'Text'. interpretProcessInputText :: InterpreterFor (ProcessInput Text) r interpretProcessInputText = interpret \case
lib/Polysemy/Process/Interpreter/ProcessOneshot.hs view
@@ -1,4 +1,4 @@--- |Description: Process Interpreters, Internal+-- | Description: Process Interpreters, Internal module Polysemy.Process.Interpreter.ProcessOneshot where import Polysemy.Conc.Effect.Race (Race)@@ -13,7 +13,7 @@ import Polysemy.Process.Interpreter.ProcessIO (ProcessIO) import Polysemy.Process.Interpreter.SystemProcess (SysProcConf, interpretSystemProcessNative) --- |Interpret 'Process' with a system process resource whose file descriptors are connected to three+-- | Interpret 'Process' with a system process resource whose file descriptors are connected to three -- 'Control.Concurrent.STM.TBMQueue.TBMQueue's, deferring decoding of stdout and stderr to the interpreters of two -- 'Polysemy.Process.ProcessOutput' effects. -- Unlike 'Polysemy.Process.interpretProcess', this variant sends errors inside the scope to the individual 'Process'@@ -34,7 +34,7 @@ (\ p -> pscope @SystemProcessScopeError options (raiseUnder . proc) p) (handleProcessWithQueues terminated) --- |Variant of 'interpretProcessOneshot' that takes a static 'SysProcConf'.+-- | Variant of 'interpretProcessOneshot' that takes a static 'SysProcConf'. interpretProcessOneshot_ :: ∀ proc i o r . Members (ProcessIO i o) r =>@@ -46,7 +46,7 @@ interpretProcessOneshot_ options proc = interpretProcessOneshot options (const (pure proc)) --- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'.+-- | Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'. -- This variant is for parameterized scopes, meaning that a value of arbitrary type may be passed to -- 'Polysemy.Process.withProcessOneshotParam' which is then passed to the supplied function to produce a 'SysProcConf' -- for the native process.@@ -62,7 +62,7 @@ interpretProcessOneshot options (insertAt @0 . proc) . raiseUnder --- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'.+-- | Interpret 'Process' as a native 'Polysemy.Process.SystemProcess'. -- This variant takes a static 'SysProcConf'. interpretProcessOneshotNative_ :: ∀ i o r .
lib/Polysemy/Process/Interpreter/ProcessOutput.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |Description: ProcessOutput Interpreters, Internal+-- | Description: ProcessOutput Interpreters, Internal module Polysemy.Process.Interpreter.ProcessOutput where import qualified Data.ByteString as ByteString@@ -9,7 +9,7 @@ import Polysemy.Process.Effect.ProcessOutput (ProcessOutput (Chunk)) import qualified Polysemy.Process.Effect.ProcessOutput as ProcessOutput --- |Interpret 'ProcessOutput' by discarding any output.+-- | Interpret 'ProcessOutput' by discarding any output. interpretProcessOutputIgnore :: ∀ p a r . InterpreterFor (ProcessOutput p a) r@@ -19,7 +19,7 @@ pure ([], "") {-# inline interpretProcessOutputIgnore #-} --- |Interpret 'ProcessOutput' by immediately emitting raw 'ByteString's without accumulation.+-- | Interpret 'ProcessOutput' by immediately emitting raw 'ByteString's without accumulation. interpretProcessOutputId :: ∀ p r . InterpreterFor (ProcessOutput p ByteString) r@@ -29,7 +29,7 @@ pure ([buffer <> new], "") {-# inline interpretProcessOutputId #-} --- |Transformer for 'ProcessOutput' that lifts results into 'Left', creating 'ProcessOutput p (Either a b)' from+-- | Transformer for 'ProcessOutput' that lifts results into 'Left', creating 'ProcessOutput p (Either a b)' from -- 'ProcessOutput p a'. interpretProcessOutputLeft :: ∀ p a b r .@@ -40,7 +40,7 @@ Chunk buf new -> first (fmap Left) <$> ProcessOutput.chunk @p buf new --- |Transformer for 'ProcessOutput' that lifts results into 'Right', creating 'ProcessOutput p (Either a b)' from+-- | Transformer for 'ProcessOutput' that lifts results into 'Right', creating 'ProcessOutput p (Either a b)' from -- 'ProcessOutput p b'. interpretProcessOutputRight :: ∀ p a b r .@@ -62,7 +62,7 @@ folder a (z, Just r) = (a : z, Just r) --- |Interpret 'ProcessOutput' by emitting individual 'ByteString' lines of output.+-- | Interpret 'ProcessOutput' by emitting individual 'ByteString' lines of output. interpretProcessOutputLines :: ∀ p r . InterpreterFor (ProcessOutput p ByteString) r@@ -72,7 +72,7 @@ pure (splitLines buffer new) {-# inline interpretProcessOutputLines #-} --- |Interpret 'ProcessOutput' by immediately emitting 'Text' without accumulation.+-- | Interpret 'ProcessOutput' by immediately emitting 'Text' without accumulation. interpretProcessOutputText :: ∀ p r . InterpreterFor (ProcessOutput p Text) r@@ -82,7 +82,7 @@ pure ([decodeUtf8 (buffer <> new)], "") {-# inline interpretProcessOutputText #-} --- |Interpret 'ProcessOutput' by emitting individual 'Text' lines of output.+-- | Interpret 'ProcessOutput' by emitting individual 'Text' lines of output. interpretProcessOutputTextLines :: ∀ p r . InterpreterFor (ProcessOutput p Text) r@@ -95,7 +95,7 @@ type Parser a = ByteString -> ProcessOutputParseResult a --- |Internal helper for 'interpretProcessOutputIncremental' that repeatedly parses elements from a chunk until the+-- | Internal helper for 'interpretProcessOutputIncremental' that repeatedly parses elements from a chunk until the -- parser returns a failure or a partial result. parseMany :: Parser a ->@@ -117,7 +117,7 @@ Done a rest -> spin (cons . (Right a :)) Nothing rest --- |Whenever a chunk of output arrives, call the supplied incremental parser whose result must be converted to+-- | Whenever a chunk of output arrives, call the supplied incremental parser whose result must be converted to -- 'ProcessOutputParseResult'. -- If a partial parse result is produced, it is stored in the state and resumed when the next chunk is available. -- If parsing an @a@ succeeds, the parser recurses until it fails.
lib/Polysemy/Process/Interpreter/Pty.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |Description: Pty Interpreters, Internal+-- | Description: Pty Interpreters, Internal module Polysemy.Process.Interpreter.Pty where import Polysemy.Resume (Stop, interpretScopedResumable, stopEitherWith, stopNote, type (!!))@@ -42,7 +42,7 @@ withPty = bracket acquirePty releasePty --- |Interpret Pty as a 'System.Posix.Pty'.+-- | Interpret Pty as a 'System.Posix.Pty'. interpretPty :: Members [Resource, Embed IO] r => InterpreterFor (Scoped_ Pty !! PtyError) r
lib/Polysemy/Process/Interpreter/SystemProcess.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |Description: SystemProcess Interpreters, Internal+-- | Description: SystemProcess Interpreters, Internal module Polysemy.Process.Interpreter.SystemProcess where import Data.ByteString (hGetSome, hPut)@@ -30,11 +30,11 @@ import qualified Polysemy.Process.Effect.SystemProcess as SystemProcess import Polysemy.Process.Effect.SystemProcess (SystemProcess) --- |Convenience alias for a vanilla 'ProcessConfig', which will usually be transformed by interpreters to use 'Handle's.+-- | Convenience alias for a vanilla 'ProcessConfig', which will usually be transformed by interpreters to use 'Handle's. type SysProcConf = ProcessConfig () () () --- |Convenience alias for the 'Process' type used by native interpreters.+-- | Convenience alias for the 'Process' type used by native interpreters. type PipesProcess = Process Handle Handle Handle @@ -109,7 +109,7 @@ b -> pure b --- |Handle 'SystemProcess' with a concrete 'System.Process' with connected pipes.+-- | Handle 'SystemProcess' with a concrete 'System.Process' with connected pipes. handleSystemProcessWithProcess :: ∀ r r0 a . Members [Stop SystemProcessError, Embed IO] r =>@@ -131,7 +131,7 @@ SystemProcess.Wait -> tryStop "wait failed" (waitExitCode process) --- |Interpret 'SystemProcess' with a concrete 'System.Process' with connected pipes.+-- | Interpret 'SystemProcess' with a concrete 'System.Process' with connected pipes. interpretSystemProcessWithProcess :: ∀ r . Member (Embed IO) r =>@@ -140,7 +140,7 @@ interpretSystemProcessWithProcess process = interpretResumable (handleSystemProcessWithProcess process) --- |Interpret 'SystemProcess' as a single global 'System.Process' that's started immediately.+-- | Interpret 'SystemProcess' as a single global 'System.Process' that's started immediately. interpretSystemProcessNativeSingle :: ∀ r . Members [Stop SystemProcessScopeError, Resource, Embed IO] r =>@@ -162,7 +162,7 @@ stop (StartFailed err) {-# inline withProcConf #-} --- |Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess'+-- | Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess' -- is called and terminated when the wrapped action finishes. -- This variant is for parameterized scopes, allowing the consumer to supply a value of type @param@ to create the -- process config.@@ -174,7 +174,7 @@ interpretSystemProcessNative config = interpretScopedR (\ p u -> raise (raise (config p)) >>= withProcConf u) handleSystemProcessWithProcess --- |Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess'+-- | Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess' -- is called and terminated when the wrapped action finishes. -- This variant takes a static 'SysProcConf'. interpretSystemProcessNative_ ::@@ -185,7 +185,7 @@ interpretSystemProcessNative_ config = interpretScopedR (const (withProcess config)) handleSystemProcessWithProcess --- |Interpret 'SystemProcess' with a concrete 'System.Process' with no connection to stdio.+-- | Interpret 'SystemProcess' with a concrete 'System.Process' with no connection to stdio. interpretSystemProcessWithProcessOpaque :: ∀ i o e r . Member (Embed IO) r =>@@ -207,7 +207,7 @@ SystemProcess.Wait -> tryStop "wait failed" (waitExitCode process) --- |Interpret 'SystemProcess' as a single global 'System.Process' that's started immediately.+-- | Interpret 'SystemProcess' as a single global 'System.Process' that's started immediately. interpretSystemProcessNativeOpaqueSingle :: ∀ i o e r . Members [Resource, Embed IO] r =>@@ -217,7 +217,7 @@ withProcessOpaque config \ process -> interpretSystemProcessWithProcessOpaque process sem --- |Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess'+-- | Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess' -- is called and terminated when the wrapped action finishes. interpretSystemProcessNativeOpaque :: ∀ i o e r .
lib/Polysemy/Process/ProcessOutput.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |The utility effect 'ProcessOutput' takes care of decoding process output, getting called by the+-- | The utility effect 'ProcessOutput' takes care of decoding process output, getting called by the -- 'Polysemy.Process.Process' interpreters whenever a chunk was read, while accumulating chunks until they were decoded -- successfully. module Polysemy.Process.ProcessOutput (
lib/Polysemy/Process/Pty.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |The effect 'Pty' abstracts pseudo terminals.+-- | The effect 'Pty' abstracts pseudo terminals. module Polysemy.Process.Pty ( module Polysemy.Process.Effect.Pty, module Polysemy.Process.Interpreter.Pty,
lib/Polysemy/Process/SysProcConf.hs view
@@ -1,4 +1,4 @@--- |Constructors for 'SysProcConf', Internal+-- | Constructors for 'SysProcConf', Internal module Polysemy.Process.SysProcConf where import Path (Abs, File, Path, Rel, toFilePath)@@ -7,19 +7,19 @@ import Polysemy.Process.Interpreter.SystemProcess (SysProcConf) --- |Create a 'SysProcConf' from an executable path and a list of arguments.+-- | Create a 'SysProcConf' from an executable path and a list of arguments. processConfig :: Path Abs File -> [Text] -> SysProcConf processConfig exe args = proc (toFilePath exe) (toString <$> args) {-# inline processConfig #-} --- |Create a 'SysProcConf' from an shell command line.+-- | Create a 'SysProcConf' from an shell command line. shellConfig :: Text -> SysProcConf shellConfig cmd = shell (toString cmd) {-# inline shellConfig #-} --- |Create a 'SysProcConf' by looking up an executable in the path, and using the supplied arguments.+-- | Create a 'SysProcConf' by looking up an executable in the path, and using the supplied arguments. which :: Member (Embed IO) r => Path Rel File ->
lib/Polysemy/Process/SystemProcess.hs view
@@ -1,6 +1,6 @@ {-# options_haddock prune #-} --- |The effect 'SystemProcess' is a low-level abstraction of a native system process.+-- | The effect 'SystemProcess' is a low-level abstraction of a native system process. module Polysemy.Process.SystemProcess ( module Polysemy.Process.Effect.SystemProcess, module Polysemy.Process.Interpreter.SystemProcess,@@ -30,7 +30,7 @@ ) import Polysemy.Process.SysProcConf --- |Obtain the current process's 'Pid'.+-- | Obtain the current process's 'Pid'. currentPid :: Member (Embed IO) r => Sem r Pid
polysemy-process.cabal view
@@ -1,11 +1,11 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.35.2.+-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack name: polysemy-process-version: 0.13.0.1+version: 0.14.0.0 synopsis: Polysemy effects for system processes description: See https://hackage.haskell.org/package/polysemy-process/docs/Polysemy-Process.html category: Process@@ -36,12 +36,14 @@ Polysemy.Process.Data.PtyError Polysemy.Process.Data.PtyResources Polysemy.Process.Data.SystemProcessError+ Polysemy.Process.Effect.Interrupt Polysemy.Process.Effect.Process Polysemy.Process.Effect.ProcessInput Polysemy.Process.Effect.ProcessOutput Polysemy.Process.Effect.Pty Polysemy.Process.Effect.SystemProcess Polysemy.Process.Executable+ Polysemy.Process.Interpreter.Interrupt Polysemy.Process.Interpreter.Process Polysemy.Process.Interpreter.ProcessInput Polysemy.Process.Interpreter.ProcessIO@@ -70,6 +72,7 @@ GADTs LambdaCase LiberalTypeSynonyms+ MonadComprehensions MultiWayIf OverloadedLabels OverloadedLists@@ -92,19 +95,20 @@ NoFieldSelectors ghc-options: -Wall -Widentities -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wredundant-constraints -Wunused-type-patterns -Wunused-packages build-depends:- base ==4.*- , incipit-core ==0.5.*- , path ==0.9.*- , path-io >=1.7 && <1.9- , polysemy ==1.9.*- , polysemy-conc >=0.13.0.1 && <0.14- , polysemy-resume >=0.7 && <0.9- , polysemy-time ==0.6.*- , posix-pty ==0.2.*- , process- , stm-chans >=3 && <3.1- , typed-process >=0.2.6 && <0.3- , unix+ async >=2.2.5 && <2.3+ , base >=4.17.2.1 && <4.20+ , incipit-core >=0.4.1.0 && <0.7+ , path >=0.9.1 && <0.10+ , path-io >=0.2.0 && <1.9+ , polysemy >=1.9.0.0 && <1.10+ , polysemy-conc >=0.14.0.0 && <0.15+ , polysemy-resume >=0.7.0.0 && <0.10+ , polysemy-time >=0.5.1.0 && <0.8+ , posix-pty >=0.2.2 && <0.3+ , process >=1.6.18.0 && <1.7+ , stm-chans >=2.0.0 && <3.1+ , typed-process >=0.2.4.1 && <0.3+ , unix >=2.7.3 && <2.9 mixins: base hiding (Prelude) , incipit-core (IncipitCore as Prelude)@@ -115,6 +119,7 @@ type: exitcode-stdio-1.0 main-is: Main.hs other-modules:+ Polysemy.Process.Test.InterruptTest Polysemy.Process.Test.ProcessTest hs-source-dirs: test@@ -133,6 +138,7 @@ GADTs LambdaCase LiberalTypeSynonyms+ MonadComprehensions MultiWayIf OverloadedLabels OverloadedLists@@ -155,18 +161,20 @@ NoFieldSelectors ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Widentities -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wredundant-constraints -Wunused-type-patterns -Wunused-packages build-depends:- base ==4.*- , incipit-core ==0.5.*- , polysemy- , polysemy-conc >=0.13.0.1 && <0.14- , polysemy-plugin >=0.4.4 && <0.5+ async >=2.2.5 && <2.3+ , base >=4.17.2.1 && <4.20+ , incipit-core >=0.4.1.0 && <0.7+ , polysemy >=1.9.0.0 && <1.10+ , polysemy-conc >=0.14.0.0 && <0.15+ , polysemy-plugin >=0.4.4.0 && <0.5 , polysemy-process- , polysemy-resume >=0.7 && <0.9- , polysemy-test >=0.6 && <0.10- , polysemy-time ==0.6.*- , tasty ==1.4.*- , tasty-expected-failure ==0.12.*- , typed-process+ , polysemy-resume >=0.7.0.0 && <0.10+ , polysemy-test >=0.3.1.6 && <0.11+ , polysemy-time >=0.5.1.0 && <0.8+ , tasty >=1.4.2 && <1.5+ , tasty-expected-failure >=0.11.1.2 && <0.13+ , typed-process >=0.2.4.1 && <0.3+ , unix >=2.7.3 && <2.9 mixins: base hiding (Prelude) , incipit-core (IncipitCore as Prelude)
readme.md view
@@ -1,6 +1,6 @@ # About -This library provides [Polysemy] effects for system processes and pseudo terminals.+This library provides [Polysemy] effects for system processes, signal handling and pseudo terminals. Please visit [Hackage] for documentation.
test/Main.hs view
@@ -1,12 +1,17 @@ module Main where +import Polysemy.Process.Test.InterruptTest (test_interrupt) import Polysemy.Process.Test.ProcessTest (test_processAll)+import Polysemy.Test (unitTest) import Test.Tasty (TestTree, defaultMain, testGroup) tests :: TestTree tests = testGroup "main" [- test_processAll+ test_processAll,+ testGroup "interrupt" [+ unitTest "interrupt" test_interrupt+ ] ] main :: IO ()
+ test/Polysemy/Process/Test/InterruptTest.hs view
@@ -0,0 +1,31 @@+module Polysemy.Process.Test.InterruptTest where++import Control.Concurrent.STM (TVar, atomically, modifyTVar, newTVarIO, readTVarIO)+import Polysemy.Conc.Interpreter.Critical (interpretCritical)+import Polysemy.Conc.Interpreter.Race (interpretRace)+import Polysemy.Test (UnitTest, assertEq, runTestAuto)+import System.Posix (Handler (CatchInfoOnce), SignalInfo, installHandler, keyboardSignal, raiseSignal)++import qualified Polysemy.Process.Effect.Interrupt as Interrupt+import Polysemy.Process.Interpreter.Interrupt (interpretInterrupt)++handler :: MVar () -> TVar Int -> SignalInfo -> IO ()+handler mv tv _ = do+ atomically (modifyTVar tv (5 +))+ putMVar mv ()++test_interrupt :: UnitTest+test_interrupt = do+ runTestAuto do+ tv <- embed (newTVarIO 0)+ mv <- embed newEmptyMVar+ embed (installHandler keyboardSignal (CatchInfoOnce (handler mv tv)) Nothing)+ asyncToIOFinal $ interpretCritical $ interpretRace $ interpretInterrupt do+ Interrupt.register "test 1" do+ atomically (modifyTVar tv (3 +))+ Interrupt.register "test 2" do+ atomically (modifyTVar tv (9 +))+ embed (raiseSignal keyboardSignal)+ embed (takeMVar mv)+ result <- embed (readTVarIO tv)+ assertEq @_ @IO 17 result