• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

pcapriotti/optparse-applicative: Applicative option parser

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

pcapriotti/optparse-applicative

开源软件地址(OpenSource Url):

https://github.com/pcapriotti/optparse-applicative

开源编程语言(OpenSource Language):

Haskell 100.0%

开源软件介绍(OpenSource Introduction):

optparse-applicative

Continuous Integration status Hackage matrix Hackage page (downloads and API reference) Hackage-Deps

optparse-applicative is a haskell library for parsing options on the command line, and providing a powerful applicative interface for composing them.

optparse-applicative takes care of reading and validating the arguments passed to the command line, handling and reporting errors, generating a usage line, a comprehensive help screen, and enabling context-sensitive bash, zsh, and fish completions.

Table of Contents

Introduction

The core type in optparse-applicative is a Parser

data Parser a

instance Functor Parser
instance Applicative Parser
instance Alternative Parser

A value of type Parser a represents a specification for a set of options, which will yield a value of type a when the command line arguments are successfully parsed.

If you are familiar with parser combinator libraries like parsec, attoparsec, or the json parser aeson you will feel right at home with optparse-applicative.

If not, don't worry! All you really need to learn are a few basic parsers, and how to compose them as instances of Applicative and Alternative.

Quick Start

Here's a simple example of a parser.

import Options.Applicative

data Sample = Sample
  { hello      :: String
  , quiet      :: Bool
  , enthusiasm :: Int }

sample :: Parser Sample
sample = Sample
      <$> strOption
          ( long "hello"
         <> metavar "TARGET"
         <> help "Target for the greeting" )
      <*> switch
          ( long "quiet"
         <> short 'q'
         <> help "Whether to be quiet" )
      <*> option auto
          ( long "enthusiasm"
         <> help "How enthusiastically to greet"
         <> showDefault
         <> value 1
         <> metavar "INT" )

The parser is built using an applicative style starting from a set of basic combinators. In this example, hello is defined as an option with a String argument, while quiet is a boolean flag (called a switch) and enthusiasm gets parsed as an Int with help of the Read type class.

The parser can be used like this:

main :: IO ()
main = greet =<< execParser opts
  where
    opts = info (sample <**> helper)
      ( fullDesc
     <> progDesc "Print a greeting for TARGET"
     <> header "hello - a test for optparse-applicative" )

greet :: Sample -> IO ()
greet (Sample h False n) = putStrLn $ "Hello, " ++ h ++ replicate n '!'
greet _ = return ()

The greet function is the entry point of the program, while opts is a complete description of the program, used when generating a help text. The helper combinator takes any parser, and adds a help option to it.

The hello option in this example is mandatory since it doesn't have a default value, so running the program without any argument will display an appropriate error message and a short option summary:

Missing: --hello TARGET

Usage: hello --hello TARGET [-q|--quiet] [--enthusiasm INT]
  Print a greeting for TARGET

Running the program with the --help option will display the full help text containing a detailed list of options with descriptions

    hello - a test for optparse-applicative

    Usage: hello --hello TARGET [-q|--quiet] [--enthusiasm INT]
      Print a greeting for TARGET

    Available options:
      --hello TARGET           Target for the greeting
      -q,--quiet               Whether to be quiet
      --enthusiasm INT         How enthusiastically to greet (default: 1)
      -h,--help                Show this help text

Basics

Parsers

optparse-applicative provides a number of primitive parsers, corresponding to different posix style options, through its Builder interface. These are detailed in their own section below, for now, here's a look at a few more examples to get a feel for how parsers can be defined.

Here is a parser for a mandatory option with an argument:

target :: Parser String
target = strOption
  (  long "hello"
  <> metavar "TARGET"
  <> help "Target for the greeting" )

One can see that we are defining an option parser for a String argument, with long option name "hello", metavariable "TARGET", and the given help text. This means that the target parser defined above will require an option like

--hello world

on the command line. The metavariable and the help text will appear in the generated help text, but don't otherwise affect the behaviour of the parser.

The attributes passed to the option are called modifiers, and are composed using the semigroup operation (<>).

Options with an argument such as target are referred to as regular options, and are very common. Another type of option is a flag, the simplest of which is a boolean switch, for example:

quiet :: Parser Bool
quiet = switch ( long "quiet" <> short 'q' <> help "Whether to be quiet" )

Here we used a short modifier to specify a one-letter name for the option. This means that this switch can be set either with --quiet or -q.

Flags, unlike regular options, have no arguments. They simply return a predetermined value. For the simple switch above, this is True if the user types the flag, and False otherwise.

There are other kinds of basic parsers, and several ways to configure them. These are covered in the Builders section.

Applicative

Now we may combine the target and quiet into a single parser that accepts both options and returns a combined value. Given a type

data Options = Options
  { optTarget :: String
  , optQuiet :: Bool }

and now it's just a matter of using Applicative's apply operator (<*>) to combine the two previously defined parsers

opts :: Parser Options
opts = Options <$> target <*> quiet

No matter which parsers appear first in the sequence, options will still be parsed in whatever order they appear in the command line. A parser with such a property is sometimes called a permutation parser.

In our example, a command line like:

--target world -q

will give the same result as

-q --target world

It is this property which leads us to an Applicative interface instead of a Monadic one, as all options must be considered in parallel, and can not depend on the output of other options.

Note, however, that the order of sequencing is still somewhat significant, in that it affects the generated help text. Customisation can be achieved easily through a lambda abstraction, with Arrow notation, or by taking advantage of GHC 8's ApplicativeDo extension.

Alternative

It is also common to find programs that can be configured in different ways through the command line. A typical example is a program that can be given a text file as input, or alternatively read it directly from the standard input.

We can model this easily and effectively in Haskell using sum types:

data Input
  = FileInput FilePath
  | StdInput

run :: Input -> IO ()
run = ...

We can now define two basic parsers for the components of the sum type:

fileInput :: Parser Input
fileInput = FileInput <$> strOption
  (  long "file"
  <> short 'f'
  <> metavar "FILENAME"
  <> help "Input file" )

stdInput :: Parser Input
stdInput = flag' StdInput
  (  long "stdin"
  <> help "Read from stdin" )

As the Parser type constructor is an instance of Alternative, we can compose these parsers with a choice operator (<|>)

input :: Parser Input
input = fileInput <|> stdInput

Now --file "foo.txt" will be parsed as FileInput "foo.txt", --stdin will be parsed as StdInput, but a command line containing both options, like

--file "foo.txt" --stdin

will be rejected.

Having Applicative and Alternative instances, optparse-applicative parsers are also able to be composed with standard combinators. For example: optional :: Alternative f => f a -> f (Maybe a) will mean the user is not required to provide input for the affected Parser.

Running parsers

Before we can run a Parser, we need to wrap it into a ParserInfo structure, that specifies a number of properties that only apply to top level parsers, such as a header describing what the program does, to be displayed in the help screen.

The function info will help with this step. In the Quick Start we saw

opts :: ParserInfo Sample
opts = info (sample <**> helper)
  ( fullDesc
  <> progDesc "Print a greeting for TARGET"
  <> header "hello - a test for optparse-applicative" )

The helper parser that we added after opts just creates a dummy --help option that displays the help text. Besides that, we just set some of the fields of the ParserInfo structure with meaningful values. Now that we have a ParserInfo, we can finally run the parser. The simplest way to do so is to simply call the execParser function in your main:

main :: IO ()
main = do
  options <- execParser opts
  ...

The execParser function takes care of everything, including getting the arguments from the command line, displaying errors and help screens to the user, and exiting with an appropriate exit code.

There are other ways to run a ParserInfo, in situations where you need finer control over the behaviour of your parser, or if you want to use it in pure code. They will be covered in Custom parsing and error handling.

Builders

Builders allow you to define parsers using a convenient combinator-based syntax. We have already seen examples of builders in action, like strOption and switch, which we used to define the opts parser for our "hello" example.

Builders always take a modifier argument, which is essentially a composition of functions acting on the option, setting values for properties or adding features.

Builders work by building the option from scratch, and eventually lifting it to a single-option parser, ready to be combined with other parsers using normal Applicative and Alternative combinators.

See the haddock documentation for Options.Applicative.Builder for a full list of builders and modifiers.

There are four different kinds of options in optparse-applicative: regular options, flags, arguments, and commands. In the following, we will go over each one of these and describe the builders that can be used to create them.

Regular options

A regular option is an option which takes a single argument, parses it, and returns a value.

A regular option can have a default value, which is used as the result if the option is not found in the command line. An option without a default value is considered mandatory, and produces an error when not found.

Regular options can have long names, or short (one-character) names, which determine when the option matches and how the argument is extracted.

An option with a long name (say "output") is specified on the command line as

--output filename.txt

or

--output=filename.txt

while a short name option (say "o") can be specified with

-o filename.txt

or

-ofilename.txt

Options can have more than one name, usually one long and one short, although you are free to create options with an arbitrary combination of long and short names.

Regular options returning strings are the most common, and they can be created using the strOption builder. For example,

strOption
   ( long "output"
  <> short 'o'
  <> metavar "FILE"
  <> value "out.txt"
  <> help "Write output to FILE" )

creates a regular option with a string argument (which can be referred to as FILE in the help text and documentation), default value "out.txt", a long name "output" and a short name "o".

A regular option can return an object of any type, and takes a reader parameter which specifies how the argument should be parsed. A common reader is auto, which requires a Read instance for the return type and uses it to parse its argument. For example:

lineCount :: Parser Int
lineCount = option auto
            ( long "lines"
           <> short 'n'
           <> metavar "K"
           <> help "Output the last K lines" )

specifies a regular option with an Int argument. We added an explicit type annotation here, since without it the parser would have been polymorphic in the output type. There's usually no need to add type annotations, however, because the type will be normally inferred from the context in which the parser is used.

Further information on readers is available below.

Flags

A flag is just like a regular option, but it doesn't take any arguments, it is either present in the command line or not.

A flag has a default value and an active value. If the flag is found on the command line, the active value is returned, otherwise the default value is used. For example:

data Verbosity = Normal | Verbose

flag Normal Verbose
  ( long "verbose"
 <> short 'v'
 <> help "Enable verbose mode" )

is a flag parser returning a Verbosity value.

Simple boolean flags can be specified using the switch builder, like so:

switch
  ( long "keep-tmp-files"
 <> help "Retain all intermediate temporary files" )

There is also a flag' builder, which has no default value. This was demonstrated earlier for our --stdin flag example, and is usually used as one side of an alternative.

Another interesting use for the flag' builder is to count the number of instances on the command line, for example, verbosity settings could be specified on a scale; the following parser will count the number of instances of -v on the command line.

length <$> many (flag' () (short 'v'))

Flags can be used together after a single hyphen, so -vvv and -v -v -v will both yield 3 for the above parser.

Arguments

An argument parser specifies a positional command line argument.

The argument builder takes a reader parameter, and creates a parser which will return the parsed value every time it is passed a command line argument for which the reader succeeds. For example

argument str (metavar "FILE")

creates an argument accepting any string. To accept an arbitrary number of arguments, combine the argument builder with either the many or some combinator:

some (argument str (metavar "FILES..."))

Note that arguments starting with - are considered options by default, and will not be considered by an argument parser.

However, parsers always accept a special argument: --. When a -- is found on the command line, all the following words are considered by argument parsers, regardless of whether they start with - or not.

Arguments use the same readers as regular options.

Commands

A command can be used to specify a sub-parser to be used when a certain string is encountered in the command line.

Commands are useful to implement command line programs with multiple functions, each with its own set of options, and possibly some global options that apply to all of them. Typical examples are version control systems like git, or build tools like cabal.

Note that all the parsers appearing in a command need to have the same type. For this reason, it is often best to use a sum type which has the same structure as the command itself. For example, for the parser above, you would define a type like:

data Options = Options
  { optCommand :: Command
  , ... }

data Command
  = Add AddOptions
  | Commit CommitOptions
  ...

A command can then be created using the subparser builder (or hsubparser, which is identical but for an additional --help option on each command), and commands can be added with the command modifier. For example,

subparser
  ( command "add" (info addCommand ( progDesc "Add a file to the repository" ))
 <> command "commit" (info commitCommand ( progDesc "Record changes to the repository" ))
  )

Each command takes a full ParserInfo structure, which will be used to extract a description for this command when generating a help text.

Alternatively, you can directly return an IO action from a parser, and execute it using join from Control.Monad.

start :: String -> IO ()
stop :: IO ()

opts :: Parser (IO ())
opts = subparser
  ( command "start" (info (start <$> argument str idm) idm)
 <> command "stop"  (info (pure stop) idm) )

main :: IO ()
main = join $ execParser (info opts idm)

Modifiers

Modifiers are instances of the Semigroup and Monoid typeclasses, so they can be combined using the composition function mappend (or simply (<>)). Since different builders accept different sets of modifiers, modifiers have a type parameter that specifies which builders support it.

For example,

command :: String -> ParserInfo a -> Mod CommandFields a

can only be used with commands, as the CommandFields type argument of Mod will prevent it from being passed to builders for other types of options.

Many modifiers are polymorphic in this type argument, which means that they can be used with any builder.

Custom parsing and error handling

Parser runners

Parsers are run with the execParser family of functions — from easiest to use to most flexible these are:

execParser       :: ParserInfo a -> IO a
customExecParser :: ParserPrefs -> ParserInfo a -> IO a
execParserPure   :: ParserPrefs -> ParserInfo a -> [String] -> ParserResult a

When using the IO functions, retrieving command line arguments and handling exit codes and failure will be done automatically. When using execParserPure, the functions

handleParseResult :: ParserResult a -> IO a
overFailure :: (ParserHelp -> ParserHelp) -> ParserResult a -> ParserResult a

can be used to correctly set exit codes and display the help message; and modify the help message in the event of a failure (adding additional information for example).

Option readers

Options and Arguments require a way to interpret the string passed on the command line to the type desired. The str and auto readers are the most common way, but one can also create a custom reader that doesn't use the Read type class or return a String, and use it to parse the option. A custom reader is a value in the ReadM monad.

We provide the eitherReader :: (String -> Either String a) -> ReadM a convenience function to help create these values, where a Left will hold the error message for a parse failure.

data FluxCapacitor = ...

parseFluxCapacitor :: ReadM FluxCapacitor
parseFluxCapacitor = eitherReader $ \s -> ...

option parseFluxCapacitor ( long "flux-capacitor" )

One can also use ReadM directly, using readerAsk to obtain the command line string, and readerAbort or readerError within the ReadM monad to exit with an error message.

One nice property of eitherReader is how well it composes with attoparsec parsers with

import qualified Data.Attoparsec.Text as A
attoReadM :: A.Parser a -> ReadM a
attoReadM p = eitherReader (A.parseOnly p . T.pack)

Preferences

PrefsMods can be used to customise the look of the usage text and control when it is displayed; turn off backtracking of subparsers; and turn on disambiguation.

To use these modifications, provide them to the prefs builder, and pass the resulting preferences to one of the parser runners that take an ParserPrefs parameter, like customExecParser.

Disambiguation

It is possible to configure optparse-applicative to perform automatic disambiguation of prefixes of long options. For example, given a program foo with options --filename and --filler, typing

$ foo --fil test.txt

fails, whereas typing

$ foo --file test.txt

succeeds, and correctly identifies "file" as an unambiguous prefix of the filename option.

Option disambiguation is off by default. To enable it, use the disambiguate PrefsMod modifier as described above.

Here is a minimal example:

import Options.Applicative

sample :: Parser ()
sample = () <$
  switch (long "filename") <*
  switch (long "filler")

main :: IO ()
main = customExecParser p opts
  where
    opts = info (helper <*> sample) idm
    p = 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap