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 ExitCode
Analogous to
&&
in BashRuns the second command only if the first one returns
ExitSuccess
(.||.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode
Analogous 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.