Is possible to create a haskell expression, using the methods in optparse-applicative, that parses program options like this?
program [-a [-b]] ...
-a and -b are optionals flags (implemented using switch
), with the constraint that the -b option only is valid if -a is typed before.
Thanks
This is possible, with slight tweaks, two different ways:
-b
if you've got -a
, but you can't insist then that the -a
comes first, since optparse-applicative's <*>
combinator doesn't specify an order.-b
option follows the a
option, but you do this by implementing a
as a command, so you lose the -
in front of it.Applicative is definitely strong enough for this, since there's no need to inspect the values returned by the parsers to determine whether -b
is allowed, so >>=
is not necessary; If -a
succeeds with any output, -b
is allowed.
I'll use a data type to represent which arguments are present, but in reality these would be more meaningful.
import Options.Applicative
data A = A (Maybe B) deriving Show
data B = B deriving Show
So the options to our program maybe contain an A, which might have a B, and always have a string.
boption :: Parser (Maybe B)
boption = flag Nothing (Just B) (short 'b')
-b
can only come with -a
(any order)I'll use flag' () (short 'a')
which just insists that -a
is there, but then use *>
instead of <*>
to ignore the return value ()
and just return whatever the boption
parser returns, giving options -a [-b]
. I'll then tag that with A :: Maybe B -> A
and finally I'll make the whole thing optional
, so you have options [-a [-b]]
aoption :: Parser (Maybe A)
aoption = optional $ A <$> (flag' () (short 'a' ) *> boption)
main = execParser (info (helper <*> aoption)
(fullDesc <> progDesc "-b is only valid with -a"))
>>= print
Notice that since <*>
allows any order, we can put -a
after -b
(which isn't quite what you asked for, but works OK and makes sense for some applications).
ghci> :main -a
Just (A Nothing)
ghci> :main -a -b
Just (A (Just B))
ghci> :main -b -a
Just (A (Just B))
ghci> :main -b
Usage: <interactive> [-a] [-b]
-b is only valid with -a
*** Exception: ExitFailure 1
-b
can only follow a
You can use command
to make a subparser
which is only valid when the command string is present. You can use it to handle arguments like cabal does, so that cabal install
and cabal update
have completely different options. Since command
takes a ParserInfo
argument, any parser you can give to execParser
can be used, so you can actually nest commands arbitrarily deeply. Sadly, commands can't start with -
, so it'll be program [a [-b]] ...
instead of program [-a [-b]] ...
.
acommand :: Parser A
acommand = subparser $ command "a" (info (A <$> (helper <*> boption))
(progDesc "you can '-b' if you like with 'a'"))
main = execParser (info (helper <*> optional acommand) fullDesc) >>= print
Which runs like this:
ghci> :main
Nothing
ghci> :main a
Just (A Nothing)
ghci> :main a -b
Just (A (Just B))
ghci> :main -b a
Usage: <interactive> [COMMAND]
*** Exception: ExitFailure 1
So you have to precede -b
with a
.
I'm afraid you can't. This is precisely the scenario that Applicative
alone can't handle while Monad
can: changing the structure of later actions based on earlier results. In an applicative computation, the "shape" always needs to be known beforehand; this has some advantages (like speeding up so array combinations, or giving out a nice readable help screen for command-line options), but here it limits you to parsing "flat" options.
The interface of optparse-applicative also has Alternative
though, which does allow dependent parsing, albeit in a different way as shown by AndrewC.
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