mem-info-0.3.0.0: src/System/MemInfo/Choices.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{- |
Module : System.MemInfo.Choices
Copyright : (c) 2022 Tim Emiola
Maintainer : Tim Emiola <adetokunbo@emio.la>
SPDX-License-Identifier: BSD3
This module defines the command line flags used to control the behavior of the
__printmem__ command
-}
module System.MemInfo.Choices (
Choices (..),
Style (..),
PrintOrder (..),
cmdInfo,
getChoices,
) where
import qualified Data.Text as Text
import GHC.Generics (Generic)
import Options.Applicative (
Parser,
ParserInfo,
ReadM,
eitherReader,
execParser,
help,
helper,
info,
long,
metavar,
option,
optional,
readerError,
short,
switch,
)
import Options.Applicative.NonEmpty (some1)
import System.MemInfo.Prelude
-- | Parses the command line arguments.
getChoices :: IO Choices
getChoices = execParser cmdInfo
-- | Represents the user-specified choices extracted from the command line
data Choices = Choices
{ choiceSplitArgs :: !Bool
, choiceOnlyTotal :: !Bool
, choiceByPid :: !Bool
, choiceShowSwap :: !Bool
, choiceReversed :: !Bool
, choiceWatchSecs :: !(Maybe Natural)
, choicePidsToShow :: !(Maybe (NonEmpty ProcessID))
, choicePrintOrder :: !(Maybe PrintOrder)
, choiceStyle :: !(Maybe Style)
}
deriving (Eq, Show, Generic)
-- | Specifies a command line that when parsed will provide 'Choices'
cmdInfo :: ParserInfo Choices
cmdInfo = info (helper <*> parseChoices) mempty
parseChoices :: Parser Choices
parseChoices =
Choices
<$> parseSplitArgs
<*> parseOnlyTotal
<*> parseDiscriminateByPid
<*> parseShowSwap
<*> parseReversed
<*> optional parseWatchPeriodSecs
<*> optional parseChoicesPidsToShow
<*> optional parsePrintOrder
<*> optional parseStyle
parseChoicesPidsToShow :: Parser (NonEmpty ProcessID)
parseChoicesPidsToShow =
some1
$ option positiveNum
$ short 'p'
<> long "pids"
<> metavar "<pid1> [ -p pid2 ... -p pidN ]"
<> help "Only show memory usage of the specified PIDs"
parseSplitArgs :: Parser Bool
parseSplitArgs =
switch
$ short 's'
<> long "split-args"
<> help "Show and separate by all command line arguments"
parseOnlyTotal :: Parser Bool
parseOnlyTotal =
switch
$ short 't'
<> long "total"
<> help "Only show the total value"
parseReversed :: Parser Bool
parseReversed =
switch
$ short 'r'
<> long "reverse"
<> help "Reverses the output order so that output descends on the sorting field"
parseDiscriminateByPid :: Parser Bool
parseDiscriminateByPid =
switch
$ short 'd'
<> long "discriminate-by-pid"
<> help "Show by process rather than by program"
parseShowSwap :: Parser Bool
parseShowSwap =
switch
$ short 'S'
<> long "show_swap"
<> help "Show swap information"
parseWatchPeriodSecs :: Parser Natural
parseWatchPeriodSecs =
option positiveNum
$ short 'w'
<> long "watch"
<> metavar "N"
<> help "Measure and show memory every N seconds (N > 0)"
positiveNum :: (Read a, Ord a, Num a) => ReadM a
positiveNum =
let
checkPositive i
| i > 0 = pure i
| otherwise = readerError "Value must be greater than 0"
in
autoOrNotAllowed >>= checkPositive
parsePrintOrder :: Parser PrintOrder
parsePrintOrder =
option autoIgnoreCase
$ short 'b'
<> long "order-by"
<> metavar "< private | swap | shared | count >"
<> help "Orders the output by ascending values of the given field"
-- | Determines the order in which @MemUsages@ in a report are printed out
data PrintOrder
= Swap
| Private
| Shared
| Count
deriving (Eq, Show, Read, Generic)
parseStyle :: Parser Style
parseStyle =
option autoIgnoreCase
$ short 'y'
<> long "output-style"
<> metavar "< [normal] | csv >"
<> help (Text.unpack styleHelp)
styleHelp :: Text
styleHelp =
Text.unlines
[ "Determines how the output report is presented;"
, "'normal' is the default and is the same as if this option was omitted;"
, "'csv' outputs the usage and header rows in csv format, with all values in KiB and no 'total' row."
, "With 'csv', the --total (-t) flag is ignored"
]
-- | Determines the format style of the output
data Style
= Csv
| Normal
deriving (Eq, Show, Read, Generic)
autoIgnoreCase :: (Read a) => ReadM a
autoIgnoreCase =
let toTitle' = Text.unpack . Text.toTitle . Text.pack
in eitherReader $ readOrNotAllowed toTitle'
autoOrNotAllowed :: (Read a) => ReadM a
autoOrNotAllowed = eitherReader $ readOrNotAllowed id
readOrNotAllowed :: (Read a) => (String -> String) -> String -> Either String a
readOrNotAllowed f x = case readEither $ f x of
Left _ -> Left $ "value '" ++ x ++ "' is not permitted"
right -> right