Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
joeyh opened this issue Feb 20, 2017 · 6 comments
Open

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

joeyh opened this issue Feb 20, 2017 · 6 comments

Comments

@joeyh
Copy link

joeyh commented Feb 20, 2017

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)

@HuwCampbell
Copy link
Collaborator

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

@bbarker
Copy link

bbarker commented Sep 14, 2018

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

@HuwCampbell
Copy link
Collaborator

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.

@Ericson2314
Copy link

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

@chshersh
Copy link

chshersh commented Jul 4, 2020

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

@HuwCampbell
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants