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
.