Skip to content
This repository has been archived by the owner on Jan 1, 2022. It is now read-only.

Add support for automatic negation flags #69

Open
6 tasks
epage opened this issue Dec 6, 2021 · 7 comments
Open
6 tasks

Add support for automatic negation flags #69

epage opened this issue Dec 6, 2021 · 7 comments

Comments

@epage
Copy link
Owner

epage commented Dec 6, 2021

Issue by kbknapp
Saturday Jan 14, 2017 at 02:17 GMT
Originally opened as clap-rs/clap#815


Add a way to automatically generate flags that override (or negate) other flags. This can be done manually already, but doing so for an entire CLI can be tedious, painful, and error prone. Manually doing so will also pollute the --help output.

This proposal offers a way to automatically have these negation flags generated on a case by case basis, or across all flags in the command. This proposal also offers a way to have these negation flags listed in the --help message or hidden.

Design

A negation flag would simply take the long version of the regular flag and pre-pend no; for exmaple --follow would get a --no-follow. If a flag only specifies a short version, the no would be prepended to the short such as -L gets --no-L.

When parsing occurs, if a negation flag is found, and the negated argument was used, it functions exactly like a override that is already supported.

Functionally the following two examples are equivilant:

app.arg(Arg::with_name("regular")
        .long("follow-links")
        .help("follows symlinks")
        .overrides_with("override"))
    .arg(Arg::with_name("override)
        .long("no-follow-links")
        .help("does not follow symlinks"))

New proposal:

app.arg(Arg::with_name("regular")
        .long("follow-links")
        .help("follows symlinks")
        .overridable(true))

Concerns

There are two primary concerns with this approach.

Flags that already contian "no"

A flag which already starts with no such as --no-ignore would end up getting a double no in the form of --no-no-ignore. This actually makes sense and is consistent, but looks strange at first glance. An alternative would be to check if a flag starts with no and simply remove the no, i.e. --no-ignore becomes --ignore but this has the downside of additional processing at runtime, becomes slightly more confusing, and has a higher chance of a conflict.

Conflicting names

If a user has selected to auto-generate a negation flag, and the negating flag long conflicts with a flag already in use, a panic! will occur. Example, --ignore and --no-ignore is already defined elsewhere, and the user has selected to automaticlly generate negation flags, this will cause --ignore to generate a --no-ignore flag which already exists causing a panic!. The fix is to either not use a sweeping setting that applies ot all flags indescriminantly, or to change/remove the already defined --no-ignore flag.


Progress

  • Add AppSettings::GenerateNegationFlags which does the above, but automatically for all flags.
    • docs
    • tests
  • Add AppSettings:GenerateHiddenNegationFlags which hides all these negation flags.
    • docs
    • tests

See the discussion in BurntSushi/ripgrep#196

Relates to #748


Edit: Removed Arg::overridable(bool) because this can already be done manually by making another flag.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by jacobsvante
Friday May 21, 2021 at 08:31 GMT


Great proposal @kbknapp. Having a --no-* equivalent just feels natural when it comes to boolean values.

Perhaps this could even be the default, considering that v3 is still in beta? The user would then have to call .overridable(false) instead to get the old behavior.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by pksunkara
Friday May 21, 2021 at 13:26 GMT


Not all single flags are need overridable flags. From looking at the clis, this is a minority use case. Which is why this won't be a default behaviour.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by Emerentius
Tuesday Jun 08, 2021 at 18:21 GMT


It's actually quite useful if all flags have negation flags because you can effectively switch the default of a flag via aliases or wrappers without hardcoding the behavior, because the user can still override the choice.
It even works with nested wrappers.

For some prior art, the very popular python command line argument library click pushes users to always define a positive and a negative flag (but allows having just one):
https://click.palletsprojects.com/en/8.0.x/options/#boolean-flags

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by Mange
Tuesday Jun 08, 2021 at 18:28 GMT


I usually also always add a no variant for almost all my options, but it's that little "almost" that make me not want this to be automatic. If there was a simple opt-in argument you could set (toggleable(true) maybe?) then that would be enough for me.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by epage
Thursday Jul 22, 2021 at 08:39 GMT


Random thoughts:

  • I don't think I normally see --no-L. Instead what I tend to see is either no short form or toggling of the case (e.g. -l vs -L). Having this be different makes this a bit more complicated.
  • Sometimes I want the no format to be the default, so I'd want the no- prefix stripped.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by mkayaalp
Monday Nov 15, 2021 at 06:29 GMT


There are also instances of complementary flags using +/- as their prefix. Aside from the bash shopt and Xwayland mentioned in #2468, xterm and set (shell builtin) have many such options:

usage:  xterm [-/+132] [-/+ah] [-/+ai] [-/+aw]
    [-/+bc] [-/+bdc] [-/+cb] [-/+cjk_width] [-/+cm] [-/+cn] 
    [-/+cu] [-/+dc] [-/+fbb] [-/+fbx] [-/+fullscreen]
    [-/+hm] [-/+hold] [-/+ie] [-/+im] [-/+itc]
    [-/+j] [-/+k8] [-/+l] [-/+lc] [-/+ls] [-/+maximized] [-/+mb]
    [-/+mesg] [-/+mk_width] [-/+nul] [-/+pc] [-/+pob] [-/+rv]
    [-/+rvc] [-/+rw] [-/+s] [-/+samename] [-/+sb] [-/+sf]
    [-/+si] [-/+sk] [-/+sm] [-/+sp] [-/+t] [-/+u8] [-/+uc]
    [-/+ulc] [-/+ulit] [-/+ut] [-/+vb] [-/+wc] [-/+wf]
    <omitted other options>
set [--abefhkmnptuvxBCEHPT] [-o option-name] [argument …]
set [+abefhkmnptuvxBCEHPT] [+o option-name] [argument …]

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by epage
Monday Nov 15, 2021 at 14:35 GMT


Though that is a whole different argument scheme and is more related to #1210, especially with my comment.

However, that also points out the general challenge with trying to support automatic negation flags: there are a lot of different styles. I think the most important thing is we don't get in the way of people writing their CLI, even if it requires more boiler palte. After that is us making the core workflows and common / best practices easy to take advantage of. I think we should define the scope of what kind of automatic flags we support and limit ourselves to that or else I fear it'll either be too complicated by supporting all types or won't be present for anyone.

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

No branches or pull requests

1 participant