I'm using optparse-applicative
and I'd like to be able to parse command line arguments such as:
$ ./program -a file1 file2 -b filea fileb
i.e., two switches, both of which can take multiple arguments.
So I have a data type for my options which looks like this:
data MyOptions = MyOptions {
aFiles :: [String]
, bFiles :: [String] }
And then a Parser
like this:
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 just one argument is supplied for each switch, but if you supply a second argument you get "Invalid argument" for that second argument.
I wondered if I could kludge it by pretending that I wanted four options: a boolean switch (i.e. -a
); a list of strings; another boolean switch (i.e. -b
); and another list of strings. So I changed my data type:
data MyOptions = MyOptions {
isA :: Bool
, aFiles :: [String]
, isB :: Bool
, bFiles :: [String] }
And then modified the parser like this:
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 an explicit parser for a string list.
But now the first many (argument str (metavar "FILE"))
consumes all of the arguments, including those following the -b
switch.
So how can I write this arguments parser?
Aside from commands, optparse-applicative
follows the getopts
convention: a single argument on the command line corresponds to a single option argument. It's even a little bit more strict, since getopts
will allow multiple options with the same switch:
./program-with-getopts -i input1 -i input2 -i input3
So there's no "magic" that can help you immediately to use your program like
./program-with-magic -a 1 2 3 -b foo bar crux
since Options.Applicative.Parser
wasn't written with this in mind; it also contradicts the POSIX conventions, where options take either one argument or none.
However, you can tackle this problem from two sides: either use -a
several times, as you would in getopts
, or tell the user to use quotes:
./program-as-above -a "1 2 3" -b "foo bar crux"
# works already with your program!
To enable the multiple use of an option you have to use many
(if they're optional) or some
(if they aren't). You can even combine both variants:
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 enables you to use
./program-with-posix-style -a 1 -a "2 3" -b foo -b "foo bar"
But your proposed style isn't supported by any parsing library I know, since the position of free arguments would be ambiguous. If you really want to use -a 1 2 3 -b foo bar crux
, you have to parse the arguments yourself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With