packages feed

tricorder-0.1.0.0: src/Tricorder/Arguments.hs

module Tricorder.Arguments
    ( Command (..)
    , FollowMode (..)
    , OutputFormat (..)
    , StatusOptions (..)
    , TestOptions (..)
    , Verbosity (..)
    , WaitMode (..)
    , parseArguments
    , runArguments
    ) where

import Atelier.Effects.Arguments (Arguments, execParser)
import Effectful.Reader.Static (Reader, runReader)
import Options.Applicative
    ( Parser
    , ParserInfo
    , ReadM
    , argument
    , auto
    , command
    , eitherReader
    , flag
    , fullDesc
    , header
    , help
    , helper
    , hsubparser
    , info
    , infoOption
    , long
    , metavar
    , option
    , progDesc
    , short
    )

import Data.Text qualified as T

import Tricorder.GhcPkg.Types (ModuleName (..), SourceQuery (..))

import Tricorder.Version qualified as Version


data WaitMode
    = ShowCurrent
    | WaitForBuild
    deriving stock (Eq)


data OutputFormat
    = TextOutput
    | JsonOutput
    deriving stock (Eq)


data Verbosity
    = Concise
    | Verbose
    deriving stock (Eq)


data FollowMode
    = NoFollow
    | Follow
    deriving stock (Eq)


data StatusOptions = StatusOptions
    { wait :: WaitMode
    , format :: OutputFormat
    , verbosity :: Verbosity
    , expand :: Maybe Int
    }


data TestOptions = TestOptions
    { failedOnly :: Bool
    , wait :: WaitMode
    }


data Command
    = Start
    | Stop
    | Status StatusOptions
    | Test TestOptions
    | UI
    | Log FollowMode
    | Source [SourceQuery]


runArguments :: (Arguments :> es) => Eff (Reader Command : es) a -> Eff es a
runArguments eff = do
    args <- parseArguments
    runReader args eff


parseArguments :: (Arguments :> es) => Eff es Command
parseArguments = execParser opts


opts :: ParserInfo Command
opts =
    info (commandParser <**> versionOption <**> helper)
        $ fullDesc
            <> progDesc "tricorder — daemon-based GHCi build status"
            <> header "tricorder — robust GHCi daemon with structured querying"


versionOption :: Parser (a -> a)
versionOption = infoOption (toString Version.gitHash) (long "version" <> help "Show version and exit")


commandParser :: Parser Command
commandParser =
    hsubparser
        ( command "start" (info (pure Start) (progDesc "Start the daemon (no-op if already running)"))
            <> command "stop" (info (pure Stop) (progDesc "Stop the daemon"))
            <> command "status" (info statusParser (progDesc "Print build diagnostics (--json for machine-readable output)"))
            <> command "test-results" (info testParser (progDesc "Show output from the latest test run"))
            <> command "ui" (info (pure UI) (progDesc "Auto-refreshing terminal display"))
            <> command "log" (info logParser (progDesc "Show daemon log output"))
            <> command "source" (info sourceParser (progDesc "Print the Haskell source of one or more installed modules"))
        )


logParser :: Parser Command
logParser =
    Log
        <$> flag
            NoFollow
            Follow
            ( long "follow"
                <> short 'f'
                <> help "Keep streaming new log lines as they are written"
            )


statusParser :: Parser Command
statusParser =
    Status
        <$> ( StatusOptions
                <$> flag
                    ShowCurrent
                    WaitForBuild
                    ( long "wait"
                        <> help "Block until the current build cycle completes"
                    )
                <*> flag
                    TextOutput
                    JsonOutput
                    ( long "json"
                        <> help "Output full build state as JSON"
                    )
                <*> flag
                    Concise
                    Verbose
                    ( long "verbose"
                        <> short 'v'
                        <> help "Print full GHC message body under each diagnostic"
                    )
                <*> optional
                    ( option
                        auto
                        ( long "expand"
                            <> metavar "N"
                            <> help "Print full GHC message body for diagnostic #N"
                        )
                    )
            )


testParser :: Parser Command
testParser =
    Test
        <$> ( TestOptions
                <$> flag
                    False
                    True
                    ( long "failed"
                        <> help "Only show output from failed test suites"
                    )
                <*> flag
                    ShowCurrent
                    WaitForBuild
                    ( long "wait"
                        <> help "Block until the current build cycle completes"
                    )
            )


sourceParser :: Parser Command
sourceParser =
    Source <$> some (argument queryReader (metavar "MODULE[#FUNCTION]" <> help "Module or Module#function"))


queryReader :: ReadM SourceQuery
queryReader = eitherReader $ \s ->
    let t = toText s
        (m, rest) = T.break (== '#') t
    in  Right
            $ SourceQuery
                { moduleName = ModuleName m
                , function = if T.null rest then Nothing else Just (T.tail rest)
                }