{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} module Text.Enum.Optparse ( -- * A simple Whole Example -- $example -- * The Drivers parseIO , parseIOWithArgs , pureParse , testCLI -- * mkParserInfo , ParserDetails(..) , mkParserInfo -- * The Parser Generators , MetaVar , HelpText , FlagName , FlagChar , enumArgP , argP , argP' , enumOptP , optP , enumSwitchesP , shortEnumSwitchesP , module Text.Enum.Text ) where import Control.Applicative import Data.Char import qualified Data.Text as T import Fmt import Options.Applicative import System.Environment import Text.Enum.Text {- $example A simple but complete example: @ {\-\# LANGUAGE DeriveAnyClass #-\} {\-\# LANGUAGE DerivingVia #-\} {\-\# LANGUAGE OverloadedStrings #-\} import Fmt import Text.Enum.Optparse import Paths_optparse_enum data Choice = C_version | C_hello deriving (Bounded,Enum,EnumText,Eq,Ord,Show) deriving (Buildable,TextParsable) via UsingEnumText Choice parserDetails :: ParserDetails parserDetails = ParserDetails { _pd_desc = "optparse-enum example program" , _pd_header = "A simple optparse-enum illustrative program" , _pd_footer = "See the optparse-enum page on Hackage for details" } main :: IO () main = do choice <- parseIO parserDetails enumSwitchesP case choice of C_version -> print version C_hello -> putStrLn "Hello!" @ -} -------------------------------------------------------------------------------- -- the drivers -------------------------------------------------------------------------------- -- | making an IO parser parseIO :: ParserDetails -> Parser a -> IO a parseIO pd psr = getArgs >>= parseIOWithArgs pd psr -- | making an IO parser, specifying the arguments parseIOWithArgs :: ParserDetails -> Parser a -> [String] -> IO a parseIOWithArgs pd psr as = handleParseResult $ execParserPure (prefs idm) (mkParserInfo pd psr) as -- | making a functional parser pureParse :: ParserDetails -> Parser a -> [String] -> Maybe a pureParse pd p = getParseResult . execParserPure (prefs idm) (mkParserInfo pd p) -- | a testing helper testCLI :: Show a => ParserDetails -> Parser a -> [String] -> IO () testCLI pd psr ss = do x <- handleParseResult $ execParserPure (prefs idm) (mkParserInfo pd psr) ss print x -------------------------------------------------------------------------------- -- mkParserInfo -------------------------------------------------------------------------------- data ParserDetails = ParserDetails { _pd_desc :: String , _pd_header :: String , _pd_footer :: String } deriving (Show) -- | given a 'Parser' makes up a corresponding @ParserInfo@ mkParserInfo :: ParserDetails -> Parser a -> ParserInfo a mkParserInfo ParserDetails{..} p = info (helper <*> p) $ fullDesc <> progDesc _pd_desc <> header _pd_header <> footer _pd_footer -------------------------------------------------------------------------------- -- Parser generators -------------------------------------------------------------------------------- type MetaVar = String -- ^ name of a meta variable to be used in the docs type HelpText = String -- ^ help text type FlagName = String -- ^ name of a flag (will be forced to lower case) type FlagChar = Char -- ^ charcter used for a short flag -- | parsing an 'EnumText' argument enumArgP :: forall a . EnumText a => MetaVar -> Parser a enumArgP var = argP var hlp where hlp = T.unpack $ T.intercalate "|" $ map (fmt . build) [minBound..maxBound :: a] -- | pasring a 'TextParsable' argument argP :: TextParsable a => MetaVar -> HelpText -> Parser a argP = argP' parseText -- | pasring an 'TextParsable' argument, the parser being passed explicitly argP' :: (T.Text->Either String a) -> MetaVar -> String -> Parser a argP' prs var hlp = argument (eitherReader $ prs . T.pack) $ metavar var <> help hlp -- | parsing an 'EnumText' option enumOptP :: forall a . EnumText a => FlagChar -> MetaVar -> Parser a enumOptP c var = optP c var hlp where hlp = T.unpack $ T.intercalate "|" $ map (fmt . build) [minBound..maxBound :: a] -- | parsing a 'TextParsable' option optP :: TextParsable a => FlagChar -> FlagName -> HelpText -> Parser a optP ch nme hlp = option (eitherReader parse_string) $ metavar var <> short ch <> long lng <> help hlp where var = map toUpper nme lng = map toLower nme -- | generate mutually exclusive switches based on 'EnumText' @a@ enumSwitchesP :: EnumText a => Parser a enumSwitchesP = shortEnumSwitchesP $ const Nothing -- | generate mutually exclusive switches based on 'EnumText' @a@, with -- some short swich options as specified by the argument function shortEnumSwitchesP :: forall a . EnumText a => (a->Maybe FlagChar) -> Parser a shortEnumSwitchesP sh_f = foldr (<|>) empty $ map mk [minBound..maxBound] where mk :: a -> Parser a mk x = flag' x $ (long $ fmt $ build x) <> shrt where shrt = case sh_f x of Nothing -> mempty Just c -> short c -------------------------------------------------------------------------------- -- helpers -------------------------------------------------------------------------------- parse_string :: TextParsable a => String -> Either String a parse_string = parseText . T.pack