I am writing a shell script in Haskell using turtle and would like to know best practices on composing commands that could fail.
Now I have a case expression staircase, like so:
runRemote :: MonadIO io => Text -> Text -> io ()
runRemote oldVersion' newVersion' = sh $ do
mkdir "out"
e1 <- shell ("command " <> oldVersion') empty
case e1 of
ExitFailure n -> cleanup
ExitSuccess -> do
e2 <- shell ("command " <> newVersion') empty
case e2 of
ExitFailure n -> cleanup
ExitSuccess -> do
curDir <- pwd
cd (curDir <.> oldVersion')
e3 <- shell ("command something else") empty
case e3 of
-- ...
-- And so on...
If the case expression was expanding on a Maybe type, the solution would be to derive a Monad instance.
Is there a special reason the library author didn't already derive a Monad instance for ExitCode or is there a better way to do error handling for Haskell shell code?
One alternative is using (.&&.) and (.||.) from Turtle.Prelude.
(.&&.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCodeAnalogous to
&&in BashRuns the second command only if the first one returns
ExitSuccess
(.||.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCodeAnalogous to
||in BashRun the second command only if the first one returns
ExitFailure
They allow you to chain your commands like this (note that everything involved must return an ExitCode, including the cleanup):
(command1 .&&. command2) .||. cleanup
Or, if you need different cleanup actions in each case:
(command1 .||. cleanup1) .&&. (command2 .||. cleanup2)
By the way, it is worth noting that ExitCode is not defined by turtle but rather by base, in the System.Exit module.