spectacle-1.0.0: src/Language/Spectacle/Interaction/CLI.hs
{-# LANGUAGE OverloadedStrings #-}
-- | This module exports the 'CLI' monad, an abstraction over command-line interactions such as emitting logs and
-- messages from the model checker per options declared by a user.
--
-- @since 1.0.0
module Language.Spectacle.Interaction.CLI
( -- * The CLI Monad
CLI (CLI),
unCLI,
-- ** Running CLI
runCLI,
-- ** CLI Operations
cliPutDoc,
-- ** CLI Documents
cliResultDoc,
-- * CLI Context
ContextCLI (ContextCLI),
ctxOpts,
ctxHandle,
-- ** Construction
newContextCLI,
)
where
import Control.Monad (unless)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (MonadReader, ReaderT, asks, runReaderT)
import Prettyprinter (Doc, annotate, line, unAnnotate, (<+>))
import Prettyprinter.Render.Terminal (AnsiStyle, Color (Green, Red), bold, color, hPutDoc)
import System.IO (Handle, hClose)
import Language.Spectacle.Interaction.Options (OptsCLI, isStdout, optsLogOutput, optsOnlyTrace)
import qualified Language.Spectacle.Interaction.Options as Opts
-- ---------------------------------------------------------------------------------------------------------------------
-- | The 'CLI' monad is a @'ReaderT' 'IO'@ carrying context of command-line options.
--
-- @since 1.0.0
newtype CLI a = CLI
{unCLI :: ReaderT ContextCLI IO a}
deriving stock (Functor)
deriving
(Applicative, Monad, MonadIO, MonadReader ContextCLI)
via ReaderT ContextCLI IO
-- | Lower 'CLI' into 'IO' given command-line options.
--
-- @since 1.0.0
runCLI :: CLI a -> OptsCLI -> IO a
runCLI cli opts = do
ctx <- newContextCLI opts
ret <- runReaderT (unCLI cli) ctx
unless (Opts.isStdout $ optsLogOutput opts) do
-- Close the handle to the log-output buffer created by
-- 'newContextCLI'/'handleFrom' after the command-line
-- interaction has been completed, if the handle was not
-- to System.IO.stdout
hClose (ctxHandle ctx)
pure ret
-- | @'cliPutDoc' doc@ emits the given @doc@ using CLI context's buffer handle.
--
-- @since 1.0.0
cliPutDoc :: Doc AnsiStyle -> CLI ()
cliPutDoc doc = do
handle <- asks ctxHandle
output <- asks (optsLogOutput . ctxOpts)
if isStdout output
then liftIO do
hPutDoc handle doc
else liftIO do
-- Clear 'AnsiStyle' annotations from the 'Doc' for
-- buffers other than stdout, since they can not be
-- rendered by the terminal.
hPutDoc handle (unAnnotate doc)
-- | @'cliResultDoc' succeeded@ for a boolean flag @succeeded@ indicating if the model checker encountered an error or
-- not, lays out the document containing:
--
-- * The type of run the model checker took (either "model check" or "trace").
-- * The result of the run (either "success" or "failure")
--
-- @since 1.0.0
cliResultDoc :: Bool -> CLI (Doc AnsiStyle)
cliResultDoc succeeded = do
runType <- asks (mkRunTypeDoc . ctxOpts)
pure ("specification:" <+> runType <> ":" <+> resultDoc <> line)
where
mkRunTypeDoc opts
| optsOnlyTrace opts = "trace"
| otherwise = "model check"
resultDoc
| succeeded = annotate (bold <> color Green) "success"
| otherwise = annotate (bold <> color Red) "failure"
-- ---------------------------------------------------------------------------------------------------------------------
-- | 'ContextCLI' is the collection frequently needed information for command-line interaction.
--
-- Note: the preferred method of construction for 'ContextCLI' is via 'newContextCLI'.
--
-- @since 1.0.0
data ContextCLI = ContextCLI
{ ctxOpts :: OptsCLI
, ctxHandle :: Handle
}
deriving stock (Show)
-- | Constructs a 'ContextCLI' from a set of command-line options 'OptsCLI'.
--
-- @since 1.0.0
newContextCLI :: OptsCLI -> IO ContextCLI
newContextCLI opts = ContextCLI opts <$> Opts.handleFrom (optsLogOutput opts)
{-# INLINE newContextCLI #-}