packages feed

MultiChor-1.0.1.0: examples/CLI.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImpredicativeTypes #-}

-- | This module defines `Choreo`, the monad for writing choreographies.
module CLI where

import Control.Monad (unless)
import Control.Monad.Freer
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.State (MonadState (get, put), StateT (runStateT), lift)
import Data.Typeable (Typeable, typeRep)
import Text.Read (readMaybe)

type Context = String

data CLISig m a where
  GetStr :: Context -> CLISig m String
  PutStr :: Context -> String -> CLISig m ()
  Internal :: m a -> CLISig m a

type CLI m = Freer (CLISig m)

instance (MonadIO m) => MonadIO (CLI m) where
  liftIO a = toFreer $ Internal $ liftIO a

runCLIIO :: forall m a. (MonadIO m) => CLI m a -> m a
runCLIIO = interpFreer handler
  where
    handler :: CLISig m b -> m b
    handler (GetStr prompt) = liftIO $ putStrLn prompt >> getLine
    handler (PutStr context l) = liftIO $ putStrLn $ context ++ " " ++ l
    handler (Internal m) = m

getstr :: Context -> CLI m String
getstr context = toFreer $ GetStr context

getln :: CLI m String
getln = getstr ""

getInput :: forall a m. (Read a, Typeable a) => Context -> CLI m a
getInput context = do
  str <- getstr context
  case readMaybe str of
    Just a -> return a
    a@Nothing -> error $ "Failed to read \"" ++ str ++ "\" as a " ++ show (typeRep a)

putstr :: Context -> String -> CLI m ()
putstr context l = toFreer $ PutStr context l

putNote :: Context -> CLI m ()
putNote = (`putstr` "")

putOutput :: (Show a) => Context -> a -> CLI m ()
putOutput context a = putstr context $ show a

data TTYEnv = TTYEnv
  { inputs :: [String],
    outputs :: [String]
  }

runCLIStateful :: forall m a. (MonadFail m, MonadIO m) => [String] -> CLI m a -> m ([String], a)
runCLIStateful ins tma = do
  (a, e) <- runStateT stateful TTYEnv {inputs = ins, outputs = []}
  return (reverse $ outputs e, a)
  where
    stateful :: StateT TTYEnv m a
    stateful = interpFreer handler tma
    handler :: forall b. CLISig m b -> StateT TTYEnv m b
    handler (GetStr c) = do
      env@TTYEnv {inputs} <- get
      case inputs of
        ln : lns -> do
          put env {inputs = lns}
          return ln
        [] -> error $ "No input to go with prompt " ++ show c ++ "."
    handler (PutStr _ o) = unless (null o) $ do
      env@TTYEnv {outputs = os} <- get
      put env {outputs = o : os}
    handler (Internal m) = lift m