Support exactly-one mutually exclusive options

Aperta
#513 4 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Tranquilla
Stack tecnologico
haskell
Ambito
cli

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.

Lingua principale
Haskell
Stelle
957
Fork
124
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di pcapriotti/optparse-applicative

Tutte le issue di pcapriotti/optparse-applicative

Issue simili

Altre issue su Haskell

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.