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

easy --no-switch complement to --switch

Open joeyh opened this issue 10 years ago • 6 comments

A fairly common idiom in option parsing libraries is for --no-switch to be available as a way to disable an earlier --switch on the command line.

This is useful eg, when you have a shell alias foo = foo --switch, so foo --no-switch can be used to override . Another use case is to help future-proof a program; if it later changes so --switch is enabled by default, then users of both the old and the new version can use --no-switch to get the old behavior.

This is a bit clumsy to do with optparse-applicative, unless I'm missing an easy way to do it. Here's an implementation that makes it easy. I'd be happy if this or something like it were added to your library.

-- | A switch that can be enabled using --foo and disabled using --no-foo.
--
-- The option modifier is applied to only the option that is *not* enabled
-- by default. For example:
--
-- > invertableSwitch "recursive" True (help "do not recurse into directories")
-- 
-- This example makes --recursive enabled by default, so 
-- the help is shown only for --no-recursive.
invertableSwitch 
    :: String -- ^ long option
    -> Bool -- ^ is switch enabled by default?
    -> Mod FlagFields Bool -- ^ option modifier
    -> Parser Bool
invertableSwitch longopt defv optmod = invertableSwitch' longopt defv
    (if defv then mempty else optmod)
    (if defv then optmod else mempty)

-- | Allows providing option modifiers for both --foo and --no-foo.
invertableSwitch'
    :: String -- ^ long option (eg "foo")
    -> Bool -- ^ is switch enabled by default?
    -> Mod FlagFields Bool -- ^ option modifier for --foo
    -> Mod FlagFields Bool -- ^ option modifier for --no-foo
    -> Parser Bool
invertableSwitch' longopt defv enmod dismod = collapse <$> many
    ( flag' True (enmod <> long longopt)
    <|> flag' False (dismod <> long nolongopt)
    )
  where
    nolongopt = "no-" ++ longopt
    collapse [] = defv
    collapse l = last l

joeyh avatar Aug 20 '15 21:08 joeyh

Here's the version of this from stack, which I think is pretty similar: https://github.com/commercialhaskell/stack/blob/0fa72bd825228d65f8e2c2b024d3fff0d7d792e7/src/Options/Applicative/Builder/Extra.hs#L36. Ours also accepts hidden --disable-* and --enable-* versions

borsboom avatar Aug 23 '15 23:08 borsboom

In you example of foo = bar --switch, then foo --no-switch; this is monadic parsing as the order then becomes important. I would suggest using something like the example given instead.

HuwCampbell avatar Aug 26 '15 06:08 HuwCampbell

Huw Campbell wrote:

In you example of foo = bar --switch, then foo --no-switch; this is monadic parsing as the order then becomes important.

Order can be important in applicative parsing, no monads necessary.

In my implementation sent to this bug, it's handled by getting a list of parses of values for the switch, and using only the last value.

see shy jo

joeyh avatar Aug 26 '15 15:08 joeyh

Oh ok, you're using many (optparse provides its own version of many which uses bind instead of apply, it's the one bit using bind in there and I don't want to add any more). I'll have a think about it, it looks like there's a few people who have written something along these lines before.

HuwCampbell avatar Aug 27 '15 01:08 HuwCampbell

+1 Would love this feature.

NorfairKing avatar Sep 15 '16 09:09 NorfairKing

Ping. Has there been any decision whether such a feature would be accepted?

hasufell avatar Oct 24 '20 15:10 hasufell