Support exactly-one mutually exclusive options
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
Research direction
Start with the current oneOf recommendation from #111 and the parser combinators shown in this issue. Investigate where multiple alternatives become an unknown-flag error, then define support for exactly-one parsing so multiple selections produce a clear error during parsing rather than in main; done means the no-option, one-option, and multiple-option cases have useful messages.
Written by the indexing model from the issue text.
Description
Related: #111
I want to allow the user to specify exactly one of --modified-files, --staged-files, or --all-files with a nice error. If I follow the current recommendation in #111, I get the following behavior:
oneOf
[ switch (long "modified-files")
, switch (long "staged-files")
, switch (long "all-files")
]
oneOf = foldr (<|>) empty
$ example
Missing: (--modified-files | --staged-files | --all-files)
$ example --modified-files
Works
$ example --modified-files --staged-files
Unknown flag: --staged-files
This has the correct semantics, and the error message when no options are passed is pretty good. The only issue is that passing multiple flags shows an unhelpful error message, when clearly a good error message is possible.
My current workaround is:
oneOf :: [Opt.Parser a] -> Opt.Parser a
oneOf parsers = validate <$> traverse Opt.optional parsers
where
validate results =
case catMaybes results of
[a] -> a
_ -> error $ "Expected exactly one of: " <> (Text.intercalate ", " . map Text.pack) optNames
optNames = concatMap getOptNames parsers
getOptNames :: Opt.Parser x -> [String]
getOptNames = \case
Opt.Internal.NilP _ -> []
Opt.Internal.OptP opt -> [getOptName opt]
Opt.Internal.MultP p1 p2 -> getOptNames p1 <> getOptNames p2
Opt.Internal.AltP p1 p2 -> getOptNames p1 <> getOptNames p2
Opt.Internal.BindP p _ -> getOptNames p
getOptName opt =
case Opt.Internal.optMain opt of
Opt.Internal.OptReader names _ _ | Just name <- getName names -> name
Opt.Internal.FlagReader names _ | Just name <- getName names -> name
_ -> Opt.Internal.propMetaVar $ Opt.Internal.optProps opt
getName names
| n : _ <- [n | Opt.Internal.OptLong n <- names] = Just ("--" <> n)
| c : _ <- [c | Opt.Internal.OptShort c <- names] = Just ['-', c]
| otherwise = Nothing
but it's super hacky and only fails when the result is used in main, instead of during the Parser evaluation. It would be great to have something officially supported here.
- Dominant language
- Haskell
- Stars
- 957
- Forks
- 124
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from pcapriotti/optparse-applicative
-
Difficulty 1/5 Under an hour Newbie friendliness 75/100
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
pcapriotti/optparse-applicative#524 · 2 comments ·
-
Allow replacing commands with metavar when using parserOptionGroupPossibly taken @tbidne claimed this 86 days ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 48/100
pcapriotti/optparse-applicative#523 · 1 comment · 1 reaction ·
-
Nushell CompletionsOpen
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
pcapriotti/optparse-applicative#495 · 2 comments ·
All issues in pcapriotti/optparse-applicative
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
IntersectMBO/cardano-ledger#6276 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
OpenZeppelin/canton-contracts#92 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
hasura/graphql-engine#10954 ·
Maintainers usually reply within 1 day