Optparse-applyative option with multiple values

I am using optparse-applicative and I would like to parse the command line arguments, for example:

 $ ./program -a file1 file2 -b filea fileb 

ie, two switches, both of which can take multiple arguments.

So, I have a data type for my parameters that looks like this:

 data MyOptions = MyOptions { aFiles :: [String] , bFiles :: [String] } 

And then a Parser as follows:

 config :: Parser MyOptions config = MyOptions <$> option (str >>= parseStringList) ( short 'a' <> long "aFiles" ) <*> option (str >>= parseStringList) ( short 'b' <> long "bFiles" ) parseStringList :: Monad m => String -> m [String] parseStringList = return . words 

This approach fails in that it will give the expected result when only one argument is provided for each switch, but if you specify the second argument, you will get an "Invalid argument" for this second argument.

I wondered if I could shy away from it, pretending that I needed four options: a Boolean switch (ie -a ); list of lines; another logical switch (i.e. -b ); and another list of lines. So I changed my data type:

 data MyOptions = MyOptions { isA :: Bool , aFiles :: [String] , isB :: Bool , bFiles :: [String] } 

And then changed the parser as follows:

 config :: Parser MyOptions config = MyOptions <$> switch ( short 'a' <> long "aFiles" ) <*> many (argument str (metavar "FILE")) <*> switch ( short 'b' <> long "bFiles" ) <*> many (argument str (metavar "FILE")) 

This time using the many and argument combinators instead of explicit parsing for the list of strings.

But now the first many (argument str (metavar "FILE")) consumes all arguments, including those that follow the -b switch.

So how can I write this argument parser?

+5
source share
1 answer

In addition to commands, optparse-applicative follows getopts : a single argument on the command line corresponds to a single option argument. This is even a little more strict, since getopts will allow several parameters with the same switch:

 ./program-with-getopts -i input1 -i input2 -i input3 

So, there is no β€œmagic” that can help you use your program immediately, for example

 ./program-with-magic -a 1 2 3 -b foo bar crux 

since Options.Applicative.Parser not written with this in mind; this also contradicts POSIX conventions, where options accept either one argument or none.

However, you can solve this problem from two sides: either use -a several times, as in getopts , or ask the user to use quotation marks:

 ./program-as-above -a "1 2 3" -b "foo bar crux" # works already with your program! 

To enable reuse of a parameter, you must use many (if they are not mandatory) or some (if they are not). You can combine both options:

 multiString desc = concat <$> some single where single = option (str >>= parseStringList) desc config :: Parser MyOptions config = MyOptions <$> multiString (short 'a' <> long "aFiles" <> help "Use quotes/multiple") <*> multiString (short 'b' <> long "bFiles" <> help "Use quotes/multiple") 

which allows you to use

 ./program-with-posix-style -a 1 -a "2 3" -b foo -b "foo bar" 

But your suggested style is not supported by any parsing library that I know, since the position of free arguments will be ambiguous. If you really want to use -a 1 2 3 -b foo bar crux , you need to parse the arguments yourself.

+4
source

Source: https://habr.com/ru/post/1240968/


All Articles