Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requiring one Option or another, not both #88

Open
gbitzes opened this issue Mar 19, 2018 · 21 comments
Open

Requiring one Option or another, not both #88

gbitzes opened this issue Mar 19, 2018 · 21 comments
Labels

Comments

@gbitzes
Copy link

gbitzes commented Mar 19, 2018

Hi, is there some way to require that one of { "--option1", "--option2" } is present, but not necessarily both? I looked at the documentation and examples, and couldn't figure out if this is possible. Do I need a custom callback?

By the way, excellent work on this library! Something like this was sorely needed in C++.

@henryiii
Copy link
Collaborator

Not really, the current way to do that would be to use a set, but that's a different syntax. What would a good api for that be? Maybe passing other options to the ->required() method?

@gbitzes
Copy link
Author

gbitzes commented Mar 19, 2018

I was thinking of either having a variadic require_one method in App:

auto opt1 = app->add_option( ... );
auto opt2 = app->add_option( ... );

app->require_one(opt1, opt2, opt3);
// could also be app->require_at_least(2, opt1, opt2, opt3, opt4);

Or the ability to make a Group required, such as app->require_group("Some group");

The first seems more flexible (allows more complicated networks of requirements, and overlapping sets), but the second easily allows expressing this in the auto-generated help string for groups.

But then again, we could auto-detect if a group has an "at least N" requirement.. I'm leaning towards the first approach.

I'm not really sure.. not a big fan of a variadic ->required() though, the intent doesn't seem clear when reading the code, IMHO.

Python's argparse takes the second approach.

@gbitzes gbitzes changed the title Requiring one Option or an another, not both Requiring one Option or another, not both Mar 19, 2018
@henryiii henryiii mentioned this issue Apr 6, 2018
@OlivierHartmann
Copy link

Hi,
I am not completely sure this is the same issue but quite an identical need so I comment this flow.

Here is the simple example:

#include <CLI/CLI.hpp>

int main(int argc, char **argv) {

	CLI::App app{"App to demonstrate a paradox."};

	std::vector<int> range;
	auto range_option = app.add_option("--range,-R", range, "A range")->required();

	int min, max, step = 1;
	app.add_option("--min,-m", min, "The minimum")->required()->excludes(range_option);
	app.add_option("--max,-M", max, "The maximum")->required()->excludes(range_option);
	app.add_option("--step,-s", step,  "The step", true)->excludes(range_option);

	CLI11_PARSE(app, argc, argv);
}

With "--range 1 2 3" argument, I get: "--min is required".
With "--min 2 --max 3 --step 1" arguments, I get: "--range: At least 1 required"
With "--min 2 --max 3 --step 1 --range 1 2 3" arguments, I get: "--range excludes --min"

A solution would be to remove the "required" flag but then the user may give nonsense. Is there a solution ?

Thank you.

@AlexanderAmelkin
Copy link

@henryiii, you marked this issue as closed and resolved in 1.5, but it looks like only the "not both" part is actually resolved. At least I was unable to find a way to "Require one option or another". I mean, as @OlivierHartmann shows above, you can require one AND another option to be required, but that doesn't work if they are mutually exclusive. How do you specify OR ? An example would come in handy. There is currently no example for excludes().

@henryiii
Copy link
Collaborator

This issue is still open, only part of it was resolved in 1.5, which is why it was mentioned in 1.5 but only the 1.5 release issue was closed.

I think the proper way to do this might be by expanding groups to be a full object, then allowing a group of options to just require 1 (or x) to pass. It's not in the immediate plans but a PR would be welcome, at least if well done. require_at_least is possible instead, but I'm not sure how that would internally be represented, groups might be easier/better/more transparent in the help.

@phlptp
Copy link
Collaborator

phlptp commented Feb 14, 2019

One possibility would be adding require_option functions just like require_subcommand functions to App class. Then if you wanted only one of a subset of options you could define those options in a "nameless" subcommand and specify the require_option as desired. I think the nameless subcommands could function as a group object like you mentioned already.

The app would count the number of options that had results after parsing just like it does for subcommands.

@henryiii
Copy link
Collaborator

That might work, actually. So the idea is just to require N options for an app (say, 1), and then nameless subcommands allow you to build groups. Not sure how it would look in the help, but it would be a simple addition.

@phlptp
Copy link
Collaborator

phlptp commented Feb 14, 2019

You would probably want a min and max just like for require_subcommand but it should be reasonably straightforward to implement.

@AlexanderAmelkin
Copy link

This "nameless subcommand" approach sounds to me like an ugly kludge. I would prefer the existing groups to become full-scale objects with properties. However, there definitely are cases where mutually exclusive option groups can exist within a logical options group. Then binding mutual-exclusiveness to a group wouldn't work unless groups would be able to have child groups.

@phlptp
Copy link
Collaborator

phlptp commented Jun 11, 2019

I think this issue can be closed. As best as I can tell this capability is in place now with the 1.8 release.

@sergio-eld
Copy link

I think this issue can be closed. As best as I can tell this capability is in place now with the 1.8 release.

so what do you have to use to require only one option to be provided? I can't find such info

@lczech
Copy link
Collaborator

lczech commented Apr 4, 2021

@ElDesalmado , same here, I am looking for this, and can't any information on how to implement this. Could anyone please point us to some example or explanation of this? Thanks!

@Coderx7
Copy link

Coderx7 commented May 5, 2021

@ElDesalmado and @lczech you can have a look here
as for one way of doing this here is an example :

    auto cpuOpt = app.add_flag("--cpu", isOnCpu, "Run the computation on the CPU")->ignore_case();
    app.add_flag("--gpu", isOnGpu, "Run the computation on the GPU")->ignore_case()->excludes(cpuOpt);

Now either --cpu can be used or `--gpu`` but not both at the same time!

@sergio-eld
Copy link

@Coderx7 but there is no required option. Will it work when both are with required?

@lczech
Copy link
Collaborator

lczech commented May 5, 2021

Thanks for the suggestion, @Coderx7, but this is not what @ElDesalmado and I are looking for. Excluding other options has been part of CLI11 since its early days. What we are rather interested in is a way to say "exactly one of these options HAS to be provided by the user".

To make that an actual part of the CLI11 API might be tricky. I have not yet figured out what I would like the help message for that to even look like, for example...

The way I am currently solving this is by having a simple condition in the execution of the program that checks that exactly one of the options is set, and if not, throws an exception. Works well, but compared to build-in ways it does not show up in the help as "one of them has to be set" or the like. Additionally, this way, my program already has started (printing its header etc), instead of failing early on (as is the case with other types of user input errors that CLI11 takes care of).

@phlptp
Copy link
Collaborator

phlptp commented Nov 25, 2021

Just in case this has not been answered yet
the way to do this is that the options are placed in an option group.

group=app.add_option_group("subgroup");
group->add_flag("--cpu", isOnCpu, "Run the computation on the CPU")->ignore_case();
    group->add_flag("--gpu", isOnGpu, "Run the computation on the GPU")->ignore_case();

group->require_option(1); 

The require_option(1) will state that 1 and only 1 option from the group is required. Any more or less is an error.

@phlptp phlptp reopened this Nov 25, 2021
@phlptp
Copy link
Collaborator

phlptp commented Nov 25, 2021

Reopening this issue to be closed when additional documentation on this is added to the book

@sergio-eld
Copy link

@phlptp looks like a workaround, rather than an intended way. Though thank you

@phlptp
Copy link
Collaborator

phlptp commented Nov 25, 2021

Option groups are a way to cluster different flags and options together for logical or control purposes. It may seem like a workaround for this particular case, but if you wanted 1 of 5 options, or 2 of 5 options, or 1 option from a group of 3 and 1 option from another group of 5, all are similar with option groups, whereas trying to do it through another mechanism starts to look ugly fast.

@ddiazdom
Copy link

ddiazdom commented Mar 21, 2022

Just in case this has not been answered yet the way to do this is that the options are placed in an option group.

group=app.add_option_group("subgroup");
group->add_flag("--cpu", isOnCpu, "Run the computation on the CPU")->ignore_case();
    group->add_flag("--gpu", isOnGpu, "Run the computation on the GPU")->ignore_case();

group->require_option(1); 

The require_option(1) will state that 1 and only 1 option from the group is required. Any more or less is an error.

Hi @phlptp,

This solution creates a new group in the help message with new a new subtitle. Is it possible to maintain the original help format? (i.e., the one that I have without calling add_option_group)

@battlmonstr
Copy link

battlmonstr commented Apr 4, 2023

if you wanted 1 of 5 options, or 2 of 5 options, or 1 option from a group of 3 and 1 option from another group of 5

@phlptp what do you recommend for choosing between group A or group B?
A and B are exclusive. If any option from group B is specified, then having an option from A is a mistake. Like std::variant<A, B>.

Either that, or option A vs group B (my group A has only 1 option).

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

No branches or pull requests

10 participants