optparse-applicative icon indicating copy to clipboard operation
optparse-applicative copied to clipboard

apparently no way to implement --foo[=val]

Open joeyh opened this issue 7 years ago • 6 comments

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)

joeyh avatar Feb 20 '17 21:02 joeyh

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

HuwCampbell avatar Feb 20 '17 22:02 HuwCampbell

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).

bbarker avatar Sep 14 '18 01:09 bbarker

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.

HuwCampbell avatar Sep 21 '18 10:09 HuwCampbell

If we could require =, i.e. --foo=bar rather than --foo bar, it would resolve the grammar ambiguity.

Ericson2314 avatar Oct 31 '18 00:10 Ericson2314

@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 file foo.toml

I think optional args provide much better UX and you need to remember/implement much fewer commands if the feature is implemented.

chshersh avatar Jul 04 '20 09:07 chshersh

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"))

HuwCampbell avatar Jul 05 '20 18:07 HuwCampbell