packages feed

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

{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Axel.Eff.Process where

import Axel.Prelude

import Control.Monad ((>=>))

import qualified Data.Text as T

import qualified Polysemy as Sem

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

data Process m a 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

Sem.makeSem ''Process

runProcess ::
     (Sem.Member (Sem.Embed IO) effs)
  => Sem.Sem (Process ': effs) a
  -> Sem.Sem effs a
runProcess =
  Sem.interpret $ \case
    CreateIndependentProcess cmd ->
      Sem.embed $ 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 ->
      Sem.embed $ do
        let config = P.shell (T.unpack cmd)
        (_, _, _, processHandle) <- P.createProcess config
        pure processHandle
    GetArgs -> Sem.embed $ map T.pack <$> System.Environment.getArgs
    HandleGetContents handle -> Sem.embed $ T.pack <$> hGetContents handle
    HandleGetLine handle -> Sem.embed $ T.pack <$> hGetLine handle
    HandleIsAtEnd handle -> Sem.embed $ hIsEOF handle
    WaitOnProcess processHandle -> Sem.embed $ P.waitForProcess processHandle

readProcess ::
     (Sem.Member Process effs) => Text -> Sem.Sem 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 :: (Sem.Member Process effs) => Text -> Sem.Sem effs ExitCode
passthroughProcess = createPassthroughProcess >=> waitOnProcess