pcapriotti / optparse-applicative

Applicative option parser
BSD 3-Clause "New" or "Revised" License
914 stars 116 forks source link

combining parsers with overlapping flags #85

Open aavogt opened 10 years ago

aavogt commented 10 years ago

Could optparse-applicative provide more help when combining parsers that have the same flag names. For example:

mapOptName  :: (OptName -> OptName) -> Parser a -> Parser a
mapOptName f (OptP x) = OptP (mapOption x)
  where
    mapOption :: Option a -> Option a
    mapOption (Option r p) = Option (mapReader r) p

    mapReader :: OptReader a -> OptReader a
    mapReader (OptReader n x y) = OptReader (map f n) x y
    mapReader (FlagReader n a) = FlagReader (map f n) a
    mapReader x = x
mapOptName f (MultP x y) = MultP (mapOptName f x) (mapOptName f y)
mapOptName f (AltP x y) = AltP (mapOptName f x) (mapOptName f y)
mapOptName f (BindP x y) = BindP (mapOptName f x) (mapOptName f . y)
mapOptName f x = x

addPrefix p (OptShort c) = OptLong (p ++ "-" ++ [c])
addPrefix p (OptLong c) = OptLong (p ++ "-" ++ c)

Lets me write something like:

step :: Parser Step
step = ...

steps :: Parser (Step, Step)
steps = (,) <$> mapOptName (addPrefix "step1") step
   <*> mapOptName (addPrefix "step2") step

steps accepts arguments like --step1-x --step2-x if step accepts -x. Ideally the prefix could be left out when there is no conflict, but I think BindP gets in the way of defining a renameOptNames :: ([OptName] -> [OptName]) -> Parser a -> Parser a that sees all OptName at once.

uu-options supports +blah -blah delimiters. See section 5.2 of their techreport

pcapriotti commented 10 years ago

I'm not sure what feature you are requesting here. Can you elaborate? See also #77.

aavogt commented 10 years ago

I want something(s) better than the mapOptName pasted above. Maybe these examples help:

import Options.Applicative; import Options.Applicative.Internal; import Options.Applicative.Types
import Data.Monoid

mapOptName  :: (OptName -> OptName) -> Parser a -> Parser a
addPrefix :: String -> OptName -> OptName
-- both are defined above

step1 = (,) <$> switch (long "step1specific") <*> switch (short 'x')
step2 = (,) <$> switch (long "step2specific") <*> switch (short 'x')

steps = (,) <$> mapOptName (addPrefix "step1") step1
    <*> mapOptName (addPrefix "step2") step2

run str = execParserPure (prefs mempty) (info steps mempty) str
{- ^

>>> run ["--step1-step1specific","--step2-step2specific","--step1-x","--step2-x"]
Success ((True,True),(True,True))

The above code works. I would like if it were possible to
make the following examples work without needing to make
substantial changes to the parsers.

>>> run ["--step1specific","--step2specific","--step2-x"]
Success ((True,False),(True,True))

>>> run ["-x"]
Success ((False,True),(False,True))

>>> run ["+step1","-x","-step1","--step2specific"]
Success ((False,True),(True,False))

-}
pcapriotti commented 10 years ago

I see. This is a sensible request, as the current semantics of sequencing of parsers with overlapping option names is a bit unsatisfactory (the second option cannot be parsed until the first one is).

I think the proper way to handle this is to add a namespace field to OptName, and then take it into account when parsing. Then we could make "+foo" and "-foo" set and unset the current namespace respectively.

I'm not sure I'll be able to implement this right away, but patches are (as always) welcome :)

yaitskov commented 3 years ago

Hi,

I am also worried about current behavior. I have short name flag in a subcommand parser with same name as on level above. The flag from a subcommand parser is not available. Validation for top level flag is triggered and it terminates my program. I would like to see at least warning when short name flag is used and help produced by the library could remove short name for subcommand if it is already used and enforce user to use long version.

HuwCampbell commented 3 years ago

So I'm going to guess you're in subparser inline mode. If you move the flag to after the definitions of the commands it should work for you, as the inlined parser will be searched first.