packages feed

axel-0.0.13: src/Axel/Eff/Process.hs

{-# LANGUAGE TemplateHaskell #-}

module Axel.Eff.Process where

import Axel.Prelude

import Control.Monad ((>=>))

import qualified Data.Text as T

import Effectful ((:>))
import qualified Effectful as Eff
import qualified Effectful.Dispatch.Dynamic as Eff
import qualified Effectful.TH as Eff

import qualified System.Environment (getArgs)
import System.Exit (ExitCode)
import System.IO (Handle, hGetContents, hGetLine, hIsEOF)
import qualified System.Process as P

data Process :: Eff.Effect where
  CreateIndependentProcess
    :: Text -> Process m (Handle, Handle, Handle, P.ProcessHandle)
  CreatePassthroughProcess :: Text -> Process m P.ProcessHandle
  GetArgs :: Process m [Text]
  HandleGetContents :: Handle -> Process m Text
  HandleGetLine :: Handle -> Process m Text
  HandleIsAtEnd :: Handle -> Process m Bool
  WaitOnProcess :: P.ProcessHandle -> Process m ExitCode

Eff.makeEffect ''Process

runProcess :: (Eff.IOE :> effs) => Eff.Eff (Process ': effs) a -> Eff.Eff effs a
runProcess =
  Eff.interpret $ \_ ->
    \case
      CreateIndependentProcess cmd ->
        Eff.liftIO $ do
          let config =
                (P.shell (T.unpack cmd))
                  { P.std_in = P.CreatePipe
                  , P.std_out = P.CreatePipe
                  , P.std_err = P.CreatePipe
                  }
          -- The handles will always be created because of `CreatePipe`, so we can safely unwrap them.
          (Just stdinHandle, Just stdoutHandle, Just stderrHandle, processHandle) <-
            P.createProcess config
          pure (stdinHandle, stdoutHandle, stderrHandle, processHandle)
      CreatePassthroughProcess cmd ->
        Eff.liftIO $ do
          let config = P.shell (T.unpack cmd)
          (_, _, _, processHandle) <- P.createProcess config
          pure processHandle
      GetArgs -> Eff.liftIO $ map T.pack <$> System.Environment.getArgs
      HandleGetContents handle -> Eff.liftIO $ T.pack <$> hGetContents handle
      HandleGetLine handle -> Eff.liftIO $ T.pack <$> hGetLine handle
      HandleIsAtEnd handle -> Eff.liftIO $ hIsEOF handle
      WaitOnProcess processHandle -> Eff.liftIO $ P.waitForProcess processHandle

readProcess :: (Process :> effs) => Text -> Eff.Eff effs (ExitCode, Text, Text)
readProcess cmd = do
  (_, stdoutHandle, stderrHandle, processHandle) <- createIndependentProcess cmd
  exitCode <- waitOnProcess processHandle
  stdout <- handleGetContents stdoutHandle
  stderr <- handleGetContents stderrHandle
  pure (exitCode, stdout, stderr)

passthroughProcess :: (Process :> effs) => Text -> Eff.Eff effs ExitCode
passthroughProcess = createPassthroughProcess >=> waitOnProcess