{-# LANGUAGE CPP #-} module Options.Applicative.Builder ( -- * Parser builders -- -- | This module contains utility functions and combinators to create parsers -- for individual options. -- -- Each parser builder takes an option modifier. A modifier can be created by -- composing the basic modifiers provided by this module using the 'Monoid' -- operations 'mempty' and 'mappend', or their aliases 'idm' and '<>'. -- -- For example: -- -- > out = strOption -- > ( long "output" -- > <> short 'o' -- > <> metavar "FILENAME" ) -- -- creates a parser for an option called \"output\". subparser, strArgument, argument, flag, flag', switch, abortOption, infoOption, strOption, option, -- * Modifiers short, long, help, helpDoc, value, showDefaultWith, showDefault, metavar, noArgError, ParseError(..), hidden, internal, style, command, commandGroup, completeWith, action, completer, idm, mappend, -- * Readers -- -- | A collection of basic 'Option' readers. auto, str, maybeReader, eitherReader, disabled, readerAbort, readerError, -- * Builder for 'ParserInfo' InfoMod, fullDesc, briefDesc, header, headerDoc, footer, footerDoc, progDesc, progDescDoc, failureCode, noIntersperse, forwardOptions, allPositional, info, -- * Builder for 'ParserPrefs' PrefsMod, multiSuffix, disambiguate, showHelpOnError, showHelpOnEmpty, noBacktrack, subparserInline, columns, helpLongEquals, helpShowGlobals, prefs, defaultPrefs, -- * Types Mod, ReadM, OptionFields, FlagFields, ArgumentFields, CommandFields, HasName, HasCompleter, HasValue, HasMetavar ) where import Control.Applicative #if __GLASGOW_HASKELL__ <= 802 import Data.Semigroup hiding (option) #endif import Data.String (fromString, IsString) import Options.Applicative.Builder.Completer import Options.Applicative.Builder.Internal import Options.Applicative.Common import Options.Applicative.Types import Options.Applicative.Help.Pretty import Options.Applicative.Help.Chunk -- Readers -- -- | 'Option' reader based on the 'Read' type class. auto :: Read a => ReadM a auto = eitherReader $ \arg -> case reads arg of [(r, "")] -> return r _ -> Left $ "cannot parse value `" ++ arg ++ "'" -- | String 'Option' reader. -- -- Polymorphic over the `IsString` type class since 0.14. str :: IsString s => ReadM s str = fromString <$> readerAsk -- | Convert a function producing an 'Either' into a reader. -- -- As an example, one can create a ReadM from an attoparsec Parser -- easily with -- -- > import qualified Data.Attoparsec.Text as A -- > import qualified Data.Text as T -- > attoparsecReader :: A.Parser a -> ReadM a -- > attoparsecReader p = eitherReader (A.parseOnly p . T.pack) eitherReader :: (String -> Either String a) -> ReadM a eitherReader f = readerAsk >>= either readerError return . f -- | Convert a function producing a 'Maybe' into a reader. maybeReader :: (String -> Maybe a) -> ReadM a maybeReader f = do arg <- readerAsk maybe (readerError $ "cannot parse value `" ++ arg ++ "'") return . f $ arg -- | Null 'Option' reader. All arguments will fail validation. disabled :: ReadM a disabled = readerError "disabled option" -- modifiers -- -- | Specify a short name for an option. short :: HasName f => Char -> Mod f a short = fieldMod . name . OptShort -- | Specify a long name for an option. long :: HasName f => String -> Mod f a long = fieldMod . name . OptLong -- | Specify a default value for an option. -- -- /Note/: Because this modifier means the parser will never fail, -- do not use it with combinators such as 'some' or 'many', as -- these combinators continue until a failure occurs. -- Careless use will thus result in a hang. -- -- To display the default value, combine with showDefault or -- showDefaultWith. value :: HasValue f => a -> Mod f a value x = Mod id (DefaultProp (Just x) Nothing) id -- | Specify a function to show the default value for an option. showDefaultWith :: (a -> String) -> Mod f a showDefaultWith s = Mod id (DefaultProp Nothing (Just s)) id -- | Show the default value for this option using its 'Show' instance. showDefault :: Show a => Mod f a showDefault = showDefaultWith show -- | Specify the help text for an option. help :: String -> Mod f a help s = optionMod $ \p -> p { propHelp = paragraph s } -- | Specify the help text for an option as a 'Text.PrettyPrint.ANSI.Leijen.Doc' -- value. helpDoc :: Maybe Doc -> Mod f a helpDoc doc = optionMod $ \p -> p { propHelp = Chunk doc } -- | Specify the error to display when no argument is provided to this option. noArgError :: ParseError -> Mod OptionFields a noArgError e = fieldMod $ \p -> p { optNoArgError = const e } -- | Specify a metavariable for the argument. -- -- Metavariables have no effect on the actual parser, and only serve to specify -- the symbolic name for an argument to be displayed in the help text. metavar :: HasMetavar f => String -> Mod f a metavar var = optionMod $ \p -> p { propMetaVar = var } -- | Hide this option from the brief description. -- -- Use 'internal' to hide the option from the help text too. hidden :: Mod f a hidden = optionMod $ \p -> p { propVisibility = min Hidden (propVisibility p) } -- | Apply a function to the option description in the usage text. -- -- > import Options.Applicative.Help -- > flag' () (short 't' <> style bold) -- -- /NOTE/: This builder is more flexible than its name and example -- allude. One of the motivating examples for its addition was to -- used `const` to completely replace the usage text of an option. style :: ( Doc -> Doc ) -> Mod f a style x = optionMod $ \p -> p { propDescMod = Just x } -- | Add a command to a subparser option. -- -- Suggested usage for multiple commands is to add them to a single subparser. e.g. -- -- @ -- sample :: Parser Sample -- sample = subparser -- ( command "hello" -- (info hello (progDesc "Print greeting")) -- <> command "goodbye" -- (info goodbye (progDesc "Say goodbye")) -- ) -- @ command :: String -> ParserInfo a -> Mod CommandFields a command cmd pinfo = fieldMod $ \p -> p { cmdCommands = (cmd, pinfo) : cmdCommands p } -- | Add a description to a group of commands. -- -- Advanced feature for separating logical groups of commands on the parse line. -- -- If using the same `metavar` for each group of commands, it may yield a more -- attractive usage text combined with `hidden` for some groups. commandGroup :: String -> Mod CommandFields a commandGroup g = fieldMod $ \p -> p { cmdGroup = Just g } -- | Add a list of possible completion values. completeWith :: HasCompleter f => [String] -> Mod f a completeWith = completer . listCompleter -- | Add a bash completion action. Common actions include @file@ and -- @directory@. See -- <http://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html#Programmable-Completion-Builtins> -- for a complete list. action :: HasCompleter f => String -> Mod f a action = completer . bashCompleter -- | Add a completer to an argument. -- -- A completer is a function String -> IO String which, given a partial -- argument, returns all possible completions for that argument. completer :: HasCompleter f => Completer -> Mod f a completer f = fieldMod $ modCompleter (`mappend` f) -- parsers -- -- | Builder for a command parser. The 'command' modifier can be used to -- specify individual commands. subparser :: Mod CommandFields a -> Parser a subparser m = mkParser d g rdr where Mod _ d g = metavar "COMMAND" `mappend` m (groupName, cmds, subs) = mkCommand m rdr = CmdReader groupName cmds subs -- | Builder for an argument parser. argument :: ReadM a -> Mod ArgumentFields a -> Parser a argument p m = mkParser d g (ArgReader rdr) where (Mod f d g) = noGlobal `mappend` m ArgumentFields compl = f (ArgumentFields mempty) rdr = CReader compl p -- | Builder for a 'String' argument. strArgument :: IsString s => Mod ArgumentFields s -> Parser s strArgument = argument str -- | Builder for a flag parser. -- -- A flag that switches from a \"default value\" to an \"active value\" when -- encountered. For a simple boolean value, use `switch` instead. -- -- /Note/: Because this parser will never fail, it can not be used with -- combinators such as 'some' or 'many', as these combinators continue until -- a failure occurs. See @flag'@. flag :: a -- ^ default value -> a -- ^ active value -> Mod FlagFields a -- ^ option modifier -> Parser a flag defv actv m = flag' actv m <|> pure defv -- | Builder for a flag parser without a default value. -- -- Same as 'flag', but with no default value. In particular, this flag will -- never parse successfully by itself. -- -- It still makes sense to use it as part of a composite parser. For example -- -- > length <$> many (flag' () (short 't')) -- -- is a parser that counts the number of "-t" arguments on the command line, -- alternatively -- -- > flag' True (long "on") <|> flag' False (long "off") -- -- will require the user to enter '--on' or '--off' on the command line. flag' :: a -- ^ active value -> Mod FlagFields a -- ^ option modifier -> Parser a flag' actv (Mod f d g) = mkParser d g rdr where rdr = let fields = f (FlagFields [] actv) in FlagReader (flagNames fields) (flagActive fields) -- | Builder for a boolean flag. -- -- /Note/: Because this parser will never fail, it can not be used with -- combinators such as 'some' or 'many', as these combinators continue until -- a failure occurs. See @flag'@. -- -- > switch = flag False True switch :: Mod FlagFields Bool -> Parser Bool switch = flag False True -- | An option that always fails. -- -- When this option is encountered, the option parser immediately aborts with -- the given parse error. If you simply want to output a message, use -- 'infoOption' instead. abortOption :: ParseError -> Mod OptionFields (a -> a) -> Parser (a -> a) abortOption err m = option (readerAbort err) . (`mappend` m) $ mconcat [ noArgError err , value id , metavar "" ] -- | An option that always fails and displays a message. infoOption :: String -> Mod OptionFields (a -> a) -> Parser (a -> a) infoOption = abortOption . InfoMsg -- | Builder for an option taking a 'String' argument. strOption :: IsString s => Mod OptionFields s -> Parser s strOption = option str -- | Builder for an option using the given reader. -- -- This is a regular option, and should always have either a @long@ or -- @short@ name specified in the modifiers (or both). -- -- > nameParser = option str ( long "name" <> short 'n' ) -- option :: ReadM a -> Mod OptionFields a -> Parser a option r m = mkParser d g rdr where Mod f d g = metavar "ARG" `mappend` m fields = f (OptionFields [] mempty ExpectsArgError) crdr = CReader (optCompleter fields) r rdr = OptReader (optNames fields) crdr (optNoArgError fields) -- | Modifier for 'ParserInfo'. newtype InfoMod a = InfoMod { applyInfoMod :: ParserInfo a -> ParserInfo a } instance Monoid (InfoMod a) where mempty = InfoMod id mappend = (<>) instance Semigroup (InfoMod a) where m1 <> m2 = InfoMod $ applyInfoMod m2 . applyInfoMod m1 -- | Show a full description in the help text of this parser. fullDesc :: InfoMod a fullDesc = InfoMod $ \i -> i { infoFullDesc = True } -- | Only show a brief description in the help text of this parser. briefDesc :: InfoMod a briefDesc = InfoMod $ \i -> i { infoFullDesc = False } -- | Specify a header for this parser. header :: String -> InfoMod a header s = InfoMod $ \i -> i { infoHeader = paragraph s } -- | Specify a header for this parser as a 'Text.PrettyPrint.ANSI.Leijen.Doc' -- value. headerDoc :: Maybe Doc -> InfoMod a headerDoc doc = InfoMod $ \i -> i { infoHeader = Chunk doc } -- | Specify a footer for this parser. footer :: String -> InfoMod a footer s = InfoMod $ \i -> i { infoFooter = paragraph s } -- | Specify a footer for this parser as a 'Text.PrettyPrint.ANSI.Leijen.Doc' -- value. footerDoc :: Maybe Doc -> InfoMod a footerDoc doc = InfoMod $ \i -> i { infoFooter = Chunk doc } -- | Specify a short program description. progDesc :: String -> InfoMod a progDesc s = InfoMod $ \i -> i { infoProgDesc = paragraph s } -- | Specify a short program description as a 'Text.PrettyPrint.ANSI.Leijen.Doc' -- value. progDescDoc :: Maybe Doc -> InfoMod a progDescDoc doc = InfoMod $ \i -> i { infoProgDesc = Chunk doc } -- | Specify an exit code if a parse error occurs. failureCode :: Int -> InfoMod a failureCode n = InfoMod $ \i -> i { infoFailureCode = n } -- | Disable parsing of regular options after arguments. After a positional -- argument is parsed, all remaining options and arguments will be treated -- as a positional arguments. Not recommended in general as users often -- expect to be able to freely intersperse regular options and flags within -- command line options. noIntersperse :: InfoMod a noIntersperse = InfoMod $ \p -> p { infoPolicy = NoIntersperse } -- | Intersperse matched options and arguments normally, but allow unmatched -- options to be treated as positional arguments. -- This is sometimes useful if one is wrapping a third party cli tool and -- needs to pass options through, while also providing a handful of their -- own options. Not recommended in general as typos by the user may not -- yield a parse error and cause confusion. forwardOptions :: InfoMod a forwardOptions = InfoMod $ \p -> p { infoPolicy = ForwardOptions } -- | Disable parsing of regular options completely. All options and arguments -- will be treated as a positional arguments. Obviously not recommended in -- general as options will be unreachable. -- This is the same behaviour one sees after the "--" pseudo-argument. allPositional :: InfoMod a allPositional = InfoMod $ \p -> p { infoPolicy = AllPositionals } -- | Create a 'ParserInfo' given a 'Parser' and a modifier. info :: Parser a -> InfoMod a -> ParserInfo a info parser m = applyInfoMod m base where base = ParserInfo { infoParser = parser , infoFullDesc = True , infoProgDesc = mempty , infoHeader = mempty , infoFooter = mempty , infoFailureCode = 1 , infoPolicy = Intersperse } newtype PrefsMod = PrefsMod { applyPrefsMod :: ParserPrefs -> ParserPrefs } instance Monoid PrefsMod where mempty = PrefsMod id mappend = (<>) instance Semigroup PrefsMod where m1 <> m2 = PrefsMod $ applyPrefsMod m2 . applyPrefsMod m1 -- | Include a suffix to attach to the metavar when multiple values -- can be entered. multiSuffix :: String -> PrefsMod multiSuffix s = PrefsMod $ \p -> p { prefMultiSuffix = s } -- | Turn on disambiguation. -- -- See -- https://github.com/pcapriotti/optparse-applicative#disambiguation disambiguate :: PrefsMod disambiguate = PrefsMod $ \p -> p { prefDisambiguate = True } -- | Show full help text on any error. showHelpOnError :: PrefsMod showHelpOnError = PrefsMod $ \p -> p { prefShowHelpOnError = True } -- | Show the help text if the user enters only the program name or -- subcommand. -- -- This will suppress a "Missing:" error and show the full usage -- instead if a user just types the name of the program. showHelpOnEmpty :: PrefsMod showHelpOnEmpty = PrefsMod $ \p -> p { prefShowHelpOnEmpty = True } -- | Turn off backtracking after subcommand is parsed. noBacktrack :: PrefsMod noBacktrack = PrefsMod $ \p -> p { prefBacktrack = NoBacktrack } -- | Allow full mixing of subcommand and parent arguments by inlining -- selected subparsers into the parent parser. -- -- /NOTE:/ When this option is used, preferences for the subparser which -- effect the parser behaviour (such as noIntersperse) are ignored. subparserInline :: PrefsMod subparserInline = PrefsMod $ \p -> p { prefBacktrack = SubparserInline } -- | Set the maximum width of the generated help text. columns :: Int -> PrefsMod columns cols = PrefsMod $ \p -> p { prefColumns = cols } -- | Show equals sign, rather than space, in usage and help text for options with -- long names. helpLongEquals :: PrefsMod helpLongEquals = PrefsMod $ \p -> p { prefHelpLongEquals = True } -- | Show global help information in subparser usage helpShowGlobals :: PrefsMod helpShowGlobals = PrefsMod $ \p -> p { prefHelpShowGlobal = True} -- | Create a `ParserPrefs` given a modifier prefs :: PrefsMod -> ParserPrefs prefs m = applyPrefsMod m base where base = ParserPrefs { prefMultiSuffix = "" , prefDisambiguate = False , prefShowHelpOnError = False , prefShowHelpOnEmpty = False , prefBacktrack = Backtrack , prefColumns = 80 , prefHelpLongEquals = False , prefHelpShowGlobal = False } -- Convenience shortcuts -- | Trivial option modifier. idm :: Monoid m => m idm = mempty -- | Default preferences. defaultPrefs :: ParserPrefs defaultPrefs = prefs idm