packages feed

chiasma-0.2.0.0: lib/Chiasma/Monad/Stream.hs

module Chiasma.Monad.Stream where

import Conduit (ConduitT, Flush(..), runConduit, sinkList, yield, yieldMany, (.|))
import Control.Monad.Trans.Free (FreeT(..))
import qualified Data.Conduit.Combinators as Conduit (drop, take)

import Chiasma.Api.Class (TmuxApi(..))
import Chiasma.Codec.Decode (TmuxDecodeError)
import Chiasma.Data.Cmd (Cmd(..), Cmds(..))
import Chiasma.Data.TmuxError (TmuxError)
import qualified Chiasma.Data.TmuxError as TmuxError (TmuxError(ProcessFailed, DecodingFailed, CommandFailed))
import Chiasma.Data.TmuxThunk (TmuxThunk)
import Chiasma.Monad.EvalFreeT (evalFreeT)
import Chiasma.Native.StreamParse (TmuxOutputBlock)
import qualified Chiasma.Native.StreamParse as TmuxOutputBlock (TmuxOutputBlock(..))

type TmuxProg = FreeT TmuxThunk

type WriteCmd m =
  ConduitT (Flush Cmd) Void m ()

type ReadOutput m =
  ConduitT () TmuxOutputBlock m ()

handleProcessOutput ::
  Cmds ->
  (Text -> Either TmuxDecodeError a) ->
  [TmuxOutputBlock] ->
  Either TmuxError [a]
handleProcessOutput cs@(Cmds cmds) _ output | length output < length cmds =
  Left $ TmuxError.ProcessFailed cs "tmux terminated before all commands were processed"
handleProcessOutput cmds decode output = do
  readOutput <- foldl validate (Right []) output
  traverse decode' readOutput
  where
    validate (Left err) _ = Left err
    validate _ (TmuxOutputBlock.Success a) = Right a
    validate _ (TmuxOutputBlock.Error a) = Left $ TmuxError.CommandFailed cmds a
    decode' outputLine =
      mapLeft (TmuxError.DecodingFailed cmds outputLine) . decode $ outputLine

executeCommands ::
  MonadIO m =>
  WriteCmd m ->
  ReadOutput m ->
  (Text -> Either TmuxDecodeError a) ->
  Cmds ->
  m (Either TmuxError [a])
executeCommands writeCmd readOutput decode cs@(Cmds cmds) = do
  output <- runConduit $ do
    yieldMany (Chunk <$> reverse cmds) .| writeCmd
    yield Flush .| writeCmd
    readOutput .| Conduit.take (length cmds) .| sinkList
  return $ handleProcessOutput cs decode output

runTmuxProg ::
  MonadIO m =>
  TmuxProg m a ->
  WriteCmd m ->
  ReadOutput m ->
  m (Either TmuxError a)
runTmuxProg prog writeCmd readOutput = do
  runConduit $ readOutput .| Conduit.drop 1
  evalFreeT (executeCommands writeCmd readOutput) def prog

runTmuxE ::
  (MonadIO m, TmuxApi m api) =>
  api ->
  TmuxProg m a ->
  m (Either TmuxError a)
runTmuxE api prog =
  withTmux api (runTmuxProg prog)

runTmux ::
  (MonadIO m, MonadDeepError e TmuxError m, TmuxApi m api) =>
  api ->
  TmuxProg m a ->
  m a
runTmux api =
  hoistEither <=< runTmuxE api