packages feed

atelier-core-0.1.0.0: src/Atelier/Effects/Exit.hs

-- | An effect for terminating the program with an exit code.
--
-- 'runExit' really exits the process; 'runExitNoOp' captures the exit code
-- instead, so tests can assert on it without killing the test runner.
module Atelier.Effects.Exit
    ( -- * Effect
      Exit
    , exitWith
    , exitSuccess
    , exitFailure

      -- * Interpreters
    , runExit
    , runExitNoOp
    ) where

import Effectful (Effect, IOE)
import Effectful.Dispatch.Dynamic (interpret_, reinterpret_)
import Effectful.Error.Static (runErrorNoCallStack, throwError)
import Effectful.TH (makeEffect)
import System.Exit (ExitCode (..))

import System.Exit qualified as IO


-- | Effect for terminating the program with an exit code.
data Exit :: Effect where
    -- | Terminate the program with the given 'ExitCode'. Does not return.
    ExitWith :: ExitCode -> Exit m a


makeEffect ''Exit


-- | Exit the program successfully (exit code 0).
exitSuccess :: (Exit :> es) => Eff es a
exitSuccess = exitWith ExitSuccess


-- | Exit the program with a generic failure (exit code 1).
exitFailure :: (Exit :> es) => Eff es a
exitFailure = exitWith (ExitFailure 1)


-- | Interpret 'Exit' by actually terminating the process.
runExit :: (IOE :> es) => Eff (Exit : es) a -> Eff es a
runExit = interpret_ \(ExitWith code) -> liftIO (IO.exitWith code)


-- | Exit interpreter that captures exit calls instead of terminating the process.
-- Returns 'Left' the exit code if the action exited, 'Right' the result otherwise.
-- Useful in unit tests.
runExitNoOp :: Eff (Exit : es) a -> Eff es (Either ExitCode a)
runExitNoOp =
    reinterpret_ (runErrorNoCallStack @ExitCode) \(ExitWith code) -> throwError code