smerdyakov-0.0.0.0: src/Smerdyakov/Internal/FreeIO.hs
module Smerdyakov.Internal.FreeIO where
import Data.Monoid ((<>))
import Control.Monad.Catch
import Control.Monad.IO.Class
import GHC.Generics (Generic)
import System.Exit (ExitCode(..))
import System.Process (readCreateProcessWithExitCode, shell)
import System.IO
import Control.Monad.Free
-- I no longer remember why I cared enough to have a free monad here...
--
-- | Free monad with IO actions that are allowed in defining @give@.
--
-- You are quite likely to hit limitations; if so, send a PR adding new
-- constructors and helpers!
data ActionF e x
= Shell String (ExitCode -> String -> String -> x)
| OpenFile FilePath IOMode (Handle -> x)
| HClose Handle x
| Throw e
deriving (Functor, Generic)
-- | An error that can be thrown in the 'Action' monad.
data ActionError
= ExpectationFailure String
deriving (Eq, Show, Read, Generic)
instance Exception ActionError
type Action = Free (ActionF ActionError)
-- | Runs the shell command, returning the exit code, stdout, and stderr.
shellA :: String -> Action (ExitCode, String, String)
shellA cmd = liftF $ Shell cmd (,,)
throwA :: ActionError -> Action a
throwA = liftF . Throw
-- | Like 'shellA', but calls @throwA@ with stderr in case the command exited
-- unsucessfully, and otherwise returns just stdout.
shellWithErrA :: String -> Action String
shellWithErrA cmd = do
(e, out, err) <- shellA cmd
case e of
ExitFailure _ -> throwA . ExpectationFailure
$ "Process exited non-zero: " <> err
ExitSuccess -> return out
-- | Like 'openFile', but for 'Action'.
openFileA :: FilePath -> IOMode -> Action Handle
openFileA file mode = liftF $ OpenFile file mode id
-- | Like 'hClose', but for 'Action'
hCloseA :: Handle -> Action ()
hCloseA hdl = liftF $ HClose hdl ()
-- | Interpret an 'Action' in a 'MonadIO'.
interpretIO :: (MonadIO m, MonadThrow m) => Action a -> m a
interpretIO (Pure a) = return a
interpretIO (Free v) = case v of
Shell cmd r -> do
(a,b,c) <- liftIO $ readCreateProcessWithExitCode (shell cmd) ""
interpretIO $ r a b c
OpenFile fp mode r -> liftIO (openFile fp mode) >>= interpretIO . r
HClose hdl r -> liftIO (hClose hdl) >> interpretIO r
Throw e -> throwM e