@@ -36,7 +36,6 @@ module System.Process.Typed
36
36
, setChildGroup
37
37
, setChildUser
38
38
#endif
39
- , setCheckExitCode
40
39
41
40
-- * Stream specs
42
41
, mkStreamSpec
@@ -54,7 +53,9 @@ module System.Process.Typed
54
53
, startProcess
55
54
, stopProcess
56
55
, withProcess
56
+ , withProcess_
57
57
, readProcess
58
+ , readProcess_
58
59
, runProcess
59
60
, runProcess_
60
61
@@ -63,6 +64,8 @@ module System.Process.Typed
63
64
-- ** Process exit code
64
65
, waitExitCode
65
66
, waitExitCodeSTM
67
+ , getExitCode
68
+ , getExitCodeSTM
66
69
, checkExitCode
67
70
, checkExitCodeSTM
68
71
@@ -149,9 +152,30 @@ data ProcessConfig stdin stdout stderr = ProcessConfig
149
152
, pcChildGroup :: ! (Maybe GroupID )
150
153
, pcChildUser :: ! (Maybe UserID )
151
154
# endif
152
-
153
- , pcCheckExitCode :: ! Bool
154
155
}
156
+ instance Show (ProcessConfig stdin stdout stderr ) where
157
+ show pc = concat
158
+ [ case pcCmdSpec pc of
159
+ P. ShellCommand s -> " Shell command: " ++ s
160
+ P. RawCommand x xs -> " Raw command: " ++ unwords (map escape (x: xs))
161
+ , " \n "
162
+ , case pcWorkingDir pc of
163
+ Nothing -> " "
164
+ Just wd -> concat
165
+ [ " Run from: "
166
+ , wd
167
+ , " \n "
168
+ ]
169
+ , case pcEnv pc of
170
+ Nothing -> " "
171
+ Just e -> unlines
172
+ $ " Modified environment:"
173
+ : map (\ (k, v) -> concat [k, " =" , v]) e
174
+ ]
175
+ where
176
+ escape x
177
+ | any (`elem` " \\\" '" ) x = show x
178
+ | otherwise = x
155
179
instance (stdin ~ () , stdout ~ () , stderr ~ () )
156
180
=> IsString (ProcessConfig stdin stdout stderr ) where
157
181
fromString s
@@ -172,7 +196,7 @@ data StreamType = STInput | STOutput
172
196
-- @since 0.1.0.0
173
197
data StreamSpec (streamType :: StreamType ) a = StreamSpec
174
198
{ ssStream :: ! P. StdStream
175
- , ssCreate :: ! (Maybe Handle -> Cleanup a )
199
+ , ssCreate :: ! (ProcessConfig () () () -> Maybe Handle -> Cleanup a )
176
200
}
177
201
deriving Functor
178
202
@@ -202,13 +226,16 @@ instance Applicative Cleanup where
202
226
--
203
227
-- @since 0.1.0.0
204
228
data Process stdin stdout stderr = Process
205
- { pCleanup :: ! (IO () )
229
+ { pConfig :: ! (ProcessConfig () () () )
230
+ , pCleanup :: ! (IO () )
206
231
, pStdin :: ! stdin
207
232
, pStdout :: ! stdout
208
233
, pStderr :: ! stderr
209
234
, pHandle :: ! P. ProcessHandle
210
235
, pExitCode :: ! (TMVar ExitCode )
211
236
}
237
+ instance Show (Process stdin stdout stderr ) where
238
+ show p = " Running process: " ++ show (pConfig p)
212
239
213
240
-- | Internal helper
214
241
defaultProcessConfig :: ProcessConfig () () ()
@@ -233,8 +260,6 @@ defaultProcessConfig = ProcessConfig
233
260
, pcChildGroup = Nothing
234
261
, pcChildUser = Nothing
235
262
#endif
236
-
237
- , pcCheckExitCode = False
238
263
}
239
264
240
265
-- | Create a 'ProcessConfig' from the given command and arguments.
@@ -409,27 +434,6 @@ setChildUser
409
434
setChildUser x pc = pc { pcChildUser = Just x }
410
435
#endif
411
436
412
- -- | Should we throw an exception when the process exits with a
413
- -- non-success code?
414
- --
415
- -- If set to 'True', then when 'stopProcess' is called - either
416
- -- directly or via 'withProcess' or other wrappers - the processes
417
- -- exit code will be checked. Any exit code besides 'ExitSuccess' will
418
- -- result in an 'ExitCodeException' being thrown.
419
- --
420
- -- Default: 'False'
421
- --
422
- -- @since 0.1.0.0
423
- setCheckExitCode :: Bool
424
- -> ProcessConfig stdin stdout stderr
425
- -> ProcessConfig stdin stdout stderr
426
- setCheckExitCode x p = p { pcCheckExitCode = x }
427
-
428
- -- TODO: Instead of having this setting, we could consider just having
429
- -- alternatives to readProcess, runProcess, etc, that check the exit
430
- -- code. This could actually be a really nice convention: readProcess
431
- -- does not check, readProcess_ or readProcessCheck does.
432
-
433
437
-- | Create a new 'StreamSpec' from the given 'P.StdStream' and a
434
438
-- helper function. This function:
435
439
--
@@ -442,25 +446,25 @@ setCheckExitCode x p = p { pcCheckExitCode = x }
442
446
--
443
447
-- @since 0.1.0.0
444
448
mkStreamSpec :: P. StdStream
445
- -> (Maybe Handle -> IO (a , IO () ))
449
+ -> (ProcessConfig () () () -> Maybe Handle -> IO (a , IO () ))
446
450
-> StreamSpec streamType a
447
- mkStreamSpec ss f = StreamSpec ss (Cleanup . f )
451
+ mkStreamSpec ss f = StreamSpec ss (\ pc mh -> Cleanup (f pc mh) )
448
452
449
453
-- | A stream spec which simply inherits the stream of the parent
450
454
-- process.
451
455
--
452
456
-- @since 0.1.0.0
453
457
inherit :: StreamSpec anyStreamType ()
454
- inherit = mkStreamSpec P. Inherit (\ Nothing -> pure (() , return () ))
458
+ inherit = mkStreamSpec P. Inherit (\ _ Nothing -> pure (() , return () ))
455
459
456
460
-- | A stream spec which will close the stream for the child process.
457
461
--
458
462
-- @since 0.1.0.0
459
463
closed :: StreamSpec anyStreamType ()
460
464
#if MIN_VERSION_process(1, 4, 0)
461
- closed = mkStreamSpec P. NoStream (\ Nothing -> pure (() , return () ))
465
+ closed = mkStreamSpec P. NoStream (\ _ Nothing -> pure (() , return () ))
462
466
#else
463
- closed = mkStreamSpec P. CreatePipe (\ (Just h) -> ((() , return () ) <$ hClose h))
467
+ closed = mkStreamSpec P. CreatePipe (\ _ (Just h) -> ((() , return () ) <$ hClose h))
464
468
#endif
465
469
466
470
-- | An input stream spec which sets the input to the given
@@ -469,7 +473,7 @@ closed = mkStreamSpec P.CreatePipe (\(Just h) -> (((), return ()) <$ hClose h))
469
473
--
470
474
-- @since 0.1.0.0
471
475
byteStringInput :: L. ByteString -> StreamSpec 'STInput ()
472
- byteStringInput lbs = StreamSpec P. CreatePipe $ \ (Just h) -> Cleanup $ do
476
+ byteStringInput lbs = mkStreamSpec P. CreatePipe $ \ _ (Just h) -> do
473
477
void $ async $ do
474
478
L. hPut h lbs
475
479
hClose h
@@ -489,7 +493,7 @@ byteStringInput lbs = StreamSpec P.CreatePipe $ \(Just h) -> Cleanup $ do
489
493
--
490
494
-- @since 0.1.0.0
491
495
byteStringOutput :: StreamSpec 'STOutput (STM (Either ByteStringOutputException L. ByteString ))
492
- byteStringOutput = StreamSpec P. CreatePipe $ \ (Just h) -> Cleanup $ do
496
+ byteStringOutput = mkStreamSpec P. CreatePipe $ \ pc (Just h) -> do
493
497
mvar <- newEmptyTMVarIO
494
498
495
499
void $ async $ do
@@ -499,7 +503,7 @@ byteStringOutput = StreamSpec P.CreatePipe $ \(Just h) -> Cleanup $ do
499
503
then atomically $ putTMVar mvar $ Right $ L. fromChunks $ front []
500
504
else loop $ front . (bs: )
501
505
loop id `catch` \ e -> do
502
- atomically $ void $ tryPutTMVar mvar $ Left $ ByteStringOutputException e
506
+ atomically $ void $ tryPutTMVar mvar $ Left $ ByteStringOutputException e pc
503
507
throwIO e
504
508
505
509
return (readTMVar mvar, hClose h)
@@ -509,7 +513,7 @@ byteStringOutput = StreamSpec P.CreatePipe $ \(Just h) -> Cleanup $ do
509
513
--
510
514
-- @since 0.1.0.0
511
515
createPipe :: StreamSpec anyStreamType Handle
512
- createPipe = StreamSpec P. CreatePipe $ \ (Just h) -> Cleanup $ return (h, hClose h)
516
+ createPipe = mkStreamSpec P. CreatePipe $ \ _ (Just h) -> return (h, hClose h)
513
517
514
518
-- | Use the provided 'Handle' for the child process, and when the
515
519
-- process exits, do /not/ close it. This is useful if, for example,
@@ -518,15 +522,15 @@ createPipe = StreamSpec P.CreatePipe $ \(Just h) -> Cleanup $ return (h, hClose
518
522
--
519
523
-- @since 0.1.0.0
520
524
useHandleOpen :: Handle -> StreamSpec anyStreamType ()
521
- useHandleOpen h = StreamSpec (P. UseHandle h) $ \ Nothing -> Cleanup $ return (() , return () )
525
+ useHandleOpen h = mkStreamSpec (P. UseHandle h) $ \ _ Nothing -> return (() , return () )
522
526
523
527
-- | Use the provided 'Handle' for the child process, and when the
524
528
-- process exits, close it. If you have no reason to keep the 'Handle'
525
529
-- open, you should use this over 'useHandleOpen'.
526
530
--
527
531
-- @since 0.1.0.0
528
532
useHandleClose :: Handle -> StreamSpec anyStreamType ()
529
- useHandleClose h = StreamSpec (P. UseHandle h) $ \ Nothing -> Cleanup $ return (() , hClose h)
533
+ useHandleClose h = mkStreamSpec (P. UseHandle h) $ \ _ Nothing -> return (() , hClose h)
530
534
531
535
-- | Provide input to a process by writing to a conduit.
532
536
--
@@ -553,7 +557,7 @@ source =
553
557
startProcess :: MonadIO m
554
558
=> ProcessConfig stdin stdout stderr
555
559
-> m (Process stdin stdout stderr )
556
- startProcess ProcessConfig {.. } = liftIO $ do
560
+ startProcess pConfig' @ ProcessConfig {.. } = liftIO $ do
557
561
let cp0 =
558
562
case pcCmdSpec of
559
563
P. ShellCommand cmd -> P. shell cmd
@@ -584,16 +588,16 @@ startProcess ProcessConfig {..} = liftIO $ do
584
588
(minH, moutH, merrH, pHandle) <- P. createProcess_ " startProcess" cp
585
589
586
590
((pStdin, pStdout, pStderr), pCleanup1) <- runCleanup $ (,,)
587
- <$> ssCreate pcStdin minH
588
- <*> ssCreate pcStdout moutH
589
- <*> ssCreate pcStderr merrH
591
+ <$> ssCreate pcStdin pConfig minH
592
+ <*> ssCreate pcStdout pConfig moutH
593
+ <*> ssCreate pcStderr pConfig merrH
590
594
591
595
pExitCode <- newEmptyTMVarIO
592
596
void $ async $ do
593
597
ec <- P. waitForProcess pHandle
594
598
atomically $ putTMVar pExitCode ec
595
599
596
- let pCleanup2 = pCleanup1 `finally` do
600
+ let pCleanup = pCleanup1 `finally` do
597
601
mec <- atomically $ tryReadTMVar pExitCode
598
602
case mec of
599
603
Nothing -> do
@@ -602,24 +606,16 @@ startProcess ProcessConfig {..} = liftIO $ do
602
606
-- a SIGKILL on Unix?
603
607
void $ atomically $ readTMVar pExitCode
604
608
Just _ -> return ()
605
- pCleanup
606
- | pcCheckExitCode = do
607
- eres <- try pCleanup2
608
- ec <- atomically $ readTMVar pExitCode
609
- case (ec, eres) of
610
- (ExitSuccess , Right () ) -> return ()
611
- (ExitSuccess , Left e) -> throwIO e
612
- _ -> throwIO $ ExitCodeException ec $ either Just (const Nothing ) eres
613
- | otherwise = pCleanup2
614
609
615
610
return Process {.. }
611
+ where
612
+ pConfig = clearStreams pConfig'
616
613
617
614
-- | Close a process and release any resources acquired. This will
618
615
-- ensure 'P.terminateProcess' is called, wait for the process to
619
616
-- actually exit, and then close out resources allocated for the
620
- -- streams. In the event of any cleanup exceptions being thrown, or if
621
- -- a non-success exit code was received and 'setCheckExitCode' was
622
- -- used, this will throw an exception.
617
+ -- streams. In the event of any cleanup exceptions being thrown this
618
+ -- will throw an exception.
623
619
--
624
620
-- @since 0.1.0.0
625
621
stopProcess :: MonadIO m
@@ -637,6 +633,17 @@ withProcess :: (MonadIO m, C.MonadMask m)
637
633
-> m a
638
634
withProcess config = C. bracket (startProcess config) stopProcess
639
635
636
+ -- | Same as 'withProcess', but also calls 'checkExitCode'
637
+ --
638
+ -- @since 0.1.0.0
639
+ withProcess_ :: (MonadIO m , C. MonadMask m )
640
+ => ProcessConfig stdin stdout stderr
641
+ -> (Process stdin stdout stderr -> m a )
642
+ -> m a
643
+ withProcess_ config = C. bracket
644
+ (startProcess config)
645
+ (\ p -> stopProcess p `finally` checkExitCode p)
646
+
640
647
-- | Run a process, capture its standard output and error as a
641
648
-- 'L.ByteString', wait for it to complete, and then return its exit
642
649
-- code, output, and error.
@@ -657,6 +664,22 @@ readProcess pc =
657
664
pc' = setStdout byteStringOutput
658
665
$ setStderr byteStringOutput pc
659
666
667
+ -- | Same as 'readProcess', but instead of returning the 'ExitCode',
668
+ -- checks it with 'checkExitCode'.
669
+ --
670
+ -- @since 0.1.0.0
671
+ readProcess_ :: MonadIO m
672
+ => ProcessConfig stdin stdoutIgnored stderrIgnored
673
+ -> m (L. ByteString , L. ByteString )
674
+ readProcess_ pc =
675
+ liftIO $ withProcess pc' $ \ p -> atomically $ (,)
676
+ <$> (checkExitCodeSTM p
677
+ *> (getStdout p >>= either throwSTM return ))
678
+ <*> (getStderr p >>= either throwSTM return )
679
+ where
680
+ pc' = setStdout byteStringOutput
681
+ $ setStderr byteStringOutput pc
682
+
660
683
-- | Run the given process, wait for it to exit, and returns its
661
684
-- 'ExitCode'.
662
685
--
@@ -672,7 +695,7 @@ runProcess pc = liftIO $ withProcess pc waitExitCode
672
695
runProcess_ :: MonadIO m
673
696
=> ProcessConfig stdin stdout stderr
674
697
-> m ()
675
- runProcess_ = liftIO . void . runProcess
698
+ runProcess_ pc = liftIO $ withProcess pc checkExitCode
676
699
677
700
-- | Wait for the process to exit and then return its 'ExitCode'.
678
701
--
@@ -689,14 +712,39 @@ waitExitCodeSTM = readTMVar . pExitCode
689
712
-- | Check if a process has exited and, if so, return its 'ExitCode'.
690
713
--
691
714
-- @since 0.1.0.0
692
- checkExitCode :: MonadIO m => Process stdin stdout stderr -> m (Maybe ExitCode )
715
+ getExitCode :: MonadIO m => Process stdin stdout stderr -> m (Maybe ExitCode )
716
+ getExitCode = liftIO . atomically . getExitCodeSTM
717
+
718
+ -- | Same as 'getExitCode', but in 'STM'.
719
+ --
720
+ -- @since 0.1.0.0
721
+ getExitCodeSTM :: Process stdin stdout stderr -> STM (Maybe ExitCode )
722
+ getExitCodeSTM = tryReadTMVar . pExitCode
723
+
724
+ -- | Wait for a process to exit, and ensure that it exited
725
+ -- successfully. If not, throws an 'ExitCodeException'.
726
+ --
727
+ -- @since 0.1.0.0
728
+ checkExitCode :: MonadIO m => Process stdin stdout stderr -> m ()
693
729
checkExitCode = liftIO . atomically . checkExitCodeSTM
694
730
695
731
-- | Same as 'checkExitCode', but in 'STM'.
696
732
--
697
733
-- @since 0.1.0.0
698
- checkExitCodeSTM :: Process stdin stdout stderr -> STM (Maybe ExitCode )
699
- checkExitCodeSTM = tryReadTMVar . pExitCode
734
+ checkExitCodeSTM :: Process stdin stdout stderr -> STM ()
735
+ checkExitCodeSTM p = do
736
+ ec <- readTMVar (pExitCode p)
737
+ case ec of
738
+ ExitSuccess -> return ()
739
+ _ -> throwSTM (ExitCodeException ec (clearStreams (pConfig p)))
740
+
741
+ -- | Internal
742
+ clearStreams :: ProcessConfig stdin stdout stderr -> ProcessConfig () () ()
743
+ clearStreams pc = pc
744
+ { pcStdin = inherit
745
+ , pcStdout = inherit
746
+ , pcStderr = inherit
747
+ }
700
748
701
749
-- | Get the child's standard input stream value.
702
750
--
@@ -716,20 +764,19 @@ getStdout = pStdout
716
764
getStderr :: Process stdin stdout stderr -> stderr
717
765
getStderr = pStderr
718
766
719
- -- | Exit code generated by 'stopProcess' when 'setCheckExitCode' is
720
- -- 'True' and a process exits with a non-success code. Contains the
721
- -- non-success code, and if any other exceptions occur during cleanup,
722
- -- that exception.
767
+ -- | Exception thrown by 'checkExitCode' in the event of a non-success
768
+ -- exit code. Note that 'checkExitCode' is called by other functions
769
+ -- as well, like 'runProcess_' or 'readProcess_'.
723
770
--
724
771
-- @since 0.1.0.0
725
- data ExitCodeException = ExitCodeException ExitCode (Maybe SomeException )
772
+ data ExitCodeException = ExitCodeException ExitCode (ProcessConfig () () () )
726
773
deriving (Show , Typeable )
727
774
instance Exception ExitCodeException
728
775
729
776
-- | Wrapper for when an exception is thrown when reading from a child
730
777
-- process, used by 'byteStringOutput'.
731
778
--
732
779
-- @since 0.1.0.0
733
- newtype ByteStringOutputException = ByteStringOutputException SomeException
780
+ data ByteStringOutputException = ByteStringOutputException SomeException ( ProcessConfig () () () )
734
781
deriving (Show , Typeable )
735
782
instance Exception ByteStringOutputException
0 commit comments