@@ -9,7 +9,8 @@
9-- | An extension of `System.Process` that integrates with logging (`Obelisk.CLI.Logging`)
9-- | An extension of `System.Process` that integrates with logging (`Obelisk.CLI.Logging`)
10-- and is thus spinner friendly.
10-- and is thus spinner friendly.
11module Obelisk.CliApp.Process
11module Obelisk.CliApp.Process
12− ( ProcessFailed (..)
12+ ( ProcessFailure (..)
13+ , AsProcessFailure (..)
13 , readProcessAndLogStderr
14 , readProcessAndLogStderr
14 , readProcessAndLogOutput
15 , readProcessAndLogOutput
15 , callProcessAndLogOutput
16 , callProcessAndLogOutput
@@ -22,6 +23,7 @@ module Obelisk.CliApp.Process
22import Control.Monad ((<=<), join, void)
23import Control.Monad ((<=<), join, void)
23import Control.Monad.Except (throwError)
24import Control.Monad.Except (throwError)
24import Control.Monad.IO.Class (MonadIO, liftIO)
25import Control.Monad.IO.Class (MonadIO, liftIO)
26+import Control.Lens (Prism', review)
25import qualified Data.ByteString as BS
27import qualified Data.ByteString as BS
26import qualified Data.ByteString.Char8 as BSC
28import qualified Data.ByteString.Char8 as BSC
27import Data.Function (fix)
29import Data.Function (fix)
@@ -41,10 +43,22 @@ import qualified System.Process as Process
42import Control.Monad.Log (Severity (..))
44import Control.Monad.Log (Severity (..))
43import Obelisk.CliApp.Logging (putLog, putLogRaw)
45import Obelisk.CliApp.Logging (putLog, putLogRaw)
44−import Obelisk.CliApp.Types (Cli, ProcessFailed (..), reconstructCommand)
46+import Obelisk.CliApp.Types (CliLog, CliThrow)
48+-- TODO put back in `Obelisk.CliApp.Process` and use prisms for extensible exceptions
49+data ProcessFailure = ProcessFailure Process.CmdSpec Int -- exit code
52+-- | Indicates arbitrary process failures form one variant (or conceptual projection) of
54+class AsProcessFailure e where
55+ asProcessFailure :: Prism' e ProcessFailure
57+instance AsProcessFailure ProcessFailure where
58+ asProcessFailure = id
46readProcessAndLogStderr
60readProcessAndLogStderr
47− :: (MonadIO m, Cli m)
61+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
48 => Severity -> CreateProcess -> m Text
62 => Severity -> CreateProcess -> m Text
49readProcessAndLogStderr sev process = do
63readProcessAndLogStderr sev process = do
50 (out, _err) <- withProcess process $ \_out err -> do
64 (out, _err) <- withProcess process $ \_out err -> do
@@ -58,9 +72,8 @@ readProcessAndLogStderr sev process = do
58-- some processes are known to spit out diagnostic or informative messages in stderr, in
72-- some processes are known to spit out diagnostic or informative messages in stderr, in
59-- which case it is advisable to call it with a non-Error severity for stderr, like
73-- which case it is advisable to call it with a non-Error severity for stderr, like
60-- `callProcessAndLogOutput (Debug, Debug)`.
74-- `callProcessAndLogOutput (Debug, Debug)`.
62readProcessAndLogOutput
75readProcessAndLogOutput
63− :: (MonadIO m, Cli m)
76+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
64 => (Severity, Severity) -> CreateProcess -> m Text
77 => (Severity, Severity) -> CreateProcess -> m Text
65readProcessAndLogOutput (sev_out, sev_err) process = do
78readProcessAndLogOutput (sev_out, sev_err) process = do
66 (_, Just out, Just err, p) <- createProcess $ process
79 (_, Just out, Just err, p) <- createProcess $ process
@@ -73,7 +86,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do
74 liftIO (waitForProcess p) >>= \case
87 liftIO (waitForProcess p) >>= \case
75 ExitSuccess -> pure outText
88 ExitSuccess -> pure outText
76− ExitFailure code -> throwError $ Right $ ProcessFailed (cmdspec process) code
89+ ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (cmdspec process) code
78-- | Like `System.Process.callProcess` but logs the combined output (stdout and stderr)
91-- | Like `System.Process.callProcess` but logs the combined output (stdout and stderr)
79-- with the corresponding severity.
92-- with the corresponding severity.
@@ -84,7 +97,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do
84-- `callProcessAndLogOutput (Debug, Debug)`.
97-- `callProcessAndLogOutput (Debug, Debug)`.
85callProcessAndLogOutput
98callProcessAndLogOutput
87− :: (MonadIO m, Cli m)
100+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
88 => (Severity, Severity) -> CreateProcess -> m ()
101 => (Severity, Severity) -> CreateProcess -> m ()
89callProcessAndLogOutput (sev_out, sev_err) process =
102callProcessAndLogOutput (sev_out, sev_err) process =
90 void $ withProcess process $ \out err -> do
103 void $ withProcess process $ \out err -> do
@@ -97,7 +110,7 @@ callProcessAndLogOutput (sev_out, sev_err) process =
98-- | Like `System.Process.createProcess` but also logs (debug) the process being run
111-- | Like `System.Process.createProcess` but also logs (debug) the process being run
99createProcess
112createProcess
100− :: (MonadIO m, Cli m)
113+ :: (MonadIO m, CliLog m)
101 => CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
114 => CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
102createProcess p = do
115createProcess p = do
103 putLog Debug $ "Creating process: " <> reconstructCommand (cmdspec p)
116 putLog Debug $ "Creating process: " <> reconstructCommand (cmdspec p)
@@ -105,7 +118,7 @@ createProcess p = do
106-- | Like `System.Process.createProcess_` but also logs (debug) the process being run
119-- | Like `System.Process.createProcess_` but also logs (debug) the process being run
107createProcess_
120createProcess_
108− :: (MonadIO m, Cli m)
121+ :: (MonadIO m, CliLog m)
109 => String -> CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
122 => String -> CreateProcess -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
110createProcess_ name p = do
123createProcess_ name p = do
111 putLog Debug $ "Creating process " <> T.pack name <> ": " <> reconstructCommand (cmdspec p)
124 putLog Debug $ "Creating process " <> T.pack name <> ": " <> reconstructCommand (cmdspec p)
@@ -113,7 +126,7 @@ createProcess_ name p = do
114-- | Like `System.Process.callProcess` but also logs (debug) the process being run
127-- | Like `System.Process.callProcess` but also logs (debug) the process being run
115callProcess
128callProcess
116− :: (MonadIO m, Cli m)
129+ :: (MonadIO m, CliLog m)
117 => String -> [String] -> m ()
130 => String -> [String] -> m ()
118callProcess exe args = do
131callProcess exe args = do
119 putLog Debug $ "Calling process " <> T.pack exe <> " with args: " <> T.pack (show args)
132 putLog Debug $ "Calling process " <> T.pack exe <> " with args: " <> T.pack (show args)
@@ -121,14 +134,14 @@ callProcess exe args = do
122-- | Like `System.Process.callCommand` but also logs (debug) the command being run
135-- | Like `System.Process.callCommand` but also logs (debug) the command being run
123callCommand
136callCommand
124− :: (MonadIO m, Cli m)
137+ :: (MonadIO m, CliLog m)
125 => String -> m ()
138 => String -> m ()
126callCommand cmd = do
139callCommand cmd = do
127 putLog Debug $ "Calling command " <> T.pack cmd
140 putLog Debug $ "Calling command " <> T.pack cmd
128 liftIO $ Process.callCommand cmd
141 liftIO $ Process.callCommand cmd
130withProcess
143withProcess
131− :: (MonadIO m, Cli m)
144+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
132 => CreateProcess -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
145 => CreateProcess -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
133withProcess process f = do -- TODO: Use bracket.
146withProcess process f = do -- TODO: Use bracket.
134 -- FIXME: Using `withCreateProcess` here leads to something operating illegally on closed handles.
147 -- FIXME: Using `withCreateProcess` here leads to something operating illegally on closed handles.
@@ -139,7 +152,7 @@ withProcess process f = do -- TODO: Use bracket.
140 liftIO (waitForProcess p) >>= \case
153 liftIO (waitForProcess p) >>= \case
141 ExitSuccess -> return (out, err)
154 ExitSuccess -> return (out, err)
142− ExitFailure code -> throwError $ Right $ ProcessFailed (cmdspec process) code
155+ ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (cmdspec process) code
144-- Create an input stream from the file handle, associating each item with the given severity.
157-- Create an input stream from the file handle, associating each item with the given severity.
145streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
158streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
@@ -147,9 +160,16 @@ streamHandle sev = Streams.map (sev,) <=< handleToInputStream
148-- | Read from an input stream and log its contents
161-- | Read from an input stream and log its contents
149streamToLog
162streamToLog
150− :: (MonadIO m, Cli m)
163+ :: (MonadIO m, CliLog m)
151 => InputStream (Severity, BSC.ByteString) -> m ()
164 => InputStream (Severity, BSC.ByteString) -> m ()
152streamToLog stream = fix $ \loop -> do
165streamToLog stream = fix $ \loop -> do
153 liftIO (Streams.read stream) >>= \case
166 liftIO (Streams.read stream) >>= \case
154 Nothing -> return ()
167 Nothing -> return ()
155 Just (sev, line) -> putLogRaw sev (T.decodeUtf8 line) >> loop
168 Just (sev, line) -> putLogRaw sev (T.decodeUtf8 line) >> loop
170+reconstructCommand :: Process.CmdSpec -> Text
171+reconstructCommand (Process.ShellCommand str) = T.pack str
172+reconstructCommand (Process.RawCommand c as) = processToShellString c as
174+ processToShellString cmd args = T.unwords $ map quoteAndEscape (cmd : args)
175+ quoteAndEscape x = "'" <> T.replace "'" "'\''" (T.pack x) <> "'"