optparse-applicative
optparse-applicative copied to clipboard
apparently no way to implement --foo[=val]
It's a fairly common pattern for an option --foo to set some default value, and --foo=val to set some more specific value. (With the equals sign being required before the value; a space is ambiguous in this case.)
I cannot find a way to implement this with optparse-applicative, without renaming one of the options from --foo to --bar.
This code compiles, and usage shows "[--foo ARG] | [--foo]", but parsing "--foo" fails.
data Foo = FooStr String | FooBool Bool
parser = (FooStr <$> strOption (long "foo")) <|> (FooBool <$> switch (long "foo"))
I don't really like that as a way of implementing it even if it worked, due to the redundancy.
Could there be a version of option that takes a default value, for when no value is provided?
(#242 touches on this, but is also about a regression)
Hi,
I agree it's not ideal.
We've been asked about this one a few times and have been cautious in supporting this due to ambiguity concerns amongst other things #235 #67 #109.
But I believe you are correct in that if you enforce that the option must be specified with an = then it will be unambiguously parse-able.
Without writing anything, I believe this will require a new entry in the OptReader sum type, and a new set of builders in order to use it.
Huw
Not sure if I'm totally following as I'm new to both Haskell and this library (I guess this is a likely library to catch newbies? not sure ;-)). Anyway, I was trying something similar but for fixed values on the RHS of equals using alternatives as well:
sdlDefaultRendererP = flag' (rendererType(defaultRenderer)) (
long "renderer=default"
<> help "Use SDL's default renderer")
hico --renderer=software
Invalid option `--renderer=software'
Did you mean this?
--renderer=software
Usage: hico (--renderer=default | --renderer=software)
Welcome to Hico!
So as we can see, parsing the = seems to be unsupported (if I remove renderer= everything works as expected, except for the fact I'm not sure if I can supply a default among the alternatives, which perhaps is a question I should ask elsewhere).
I think the above is probably what is described in #242 as
Next up is a real bug, in that yes, we shouldn't pop off a flags parse word value if it's specified as a long option.
Anyway, in my particular case, I don't really need =, though I can see the desire in general (I do kind of need a way to specify a default alternative, but feel free to direct me elsewhere for that as it is a bit off topic).
This is unrelated.
So you shouldn't be using flag', nor putting = in your argument. The = sign is handled by optparse, and is equivalent to --renderer default. What you want is something more like
sdlRendererP =
option renderReader $
long "renderer" <> help "Select SDL renderer"
where
renderReader :: ReadM Renderemajig
renderReader = auto >>= \case
"default" -> pure defaultRenderer
_ -> fail "Nope"
Please open a separate issue if you need additional help. We don't want to bother Joey.
If we could require =, i.e. --foo=bar rather than --foo bar, it would resolve the grammar ambiguity.
@HuwCampbell I think this is a very important feature to have. And implementing optional arguments would increase optparse-applicative a lot. It's a huge deal! While developing tons on CLI tools I started to need optional args more and more.
I'm completely okay with the disambiguation by the = sign. This sounds reasonable to me.
A few examples of how optional args can be used:
- List issues by filter
-m|--milestone— issues from the current milestone--milestone=2— issues from milestone with ID 2
- Output to file
--toml-output— print output in TOML format in stdout--toml-output=foo.toml— output TOML to the filefoo.toml
I think optional args provide much better UX and you need to remember/implement much fewer commands if the feature is implemented.
There's currently an okish way to do this, after the fix for #242.
optArg :: String -> String -> ReadM a -> Parser (Maybe a)
optArg x meta rdr =
flag' Nothing (long x <> style (<> string ("[=" <> meta <> "]"))) <|>
option (Just <$> rdr) (long x <> internal)
two :: Parser (Maybe Int, Maybe (Maybe String))
two = (,) <$> optArg "milestone" "INT" (auto :: ReadM Int)
<*> optional (optArg "output" "FILE" (str :: ReadM String))
main :: IO ()
main = execParser (info (two <**> helper) mempty) >>= print
*Options.Applicative> :main --help
Usage: <interactive> --milestone[=INT] [--output[=FILE]]
Available options:
-h,--help Show this help text
*** Exception: ExitSuccess
*Options.Applicative> :main --milestone
(Nothing,Nothing)
*Options.Applicative> :main --milestone=2
(Just 2,Nothing)
*Options.Applicative> :main --output --milestone=2
(Just 2,Just Nothing)
*Options.Applicative> :main --output=here.toml --milestone=2
(Just 2,Just (Just "here.toml"))