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

Bug when grouping positional args with options #1794

Open
nayeemrmn opened this issue Apr 8, 2020 · 14 comments · Fixed by #1856 or #2245
Open

Bug when grouping positional args with options #1794

nayeemrmn opened this issue Apr 8, 2020 · 14 comments · Fixed by #1856 or #2245
Assignees
Labels
A-help Area: documentation, including docs.rs, readme, examples, etc... C-bug Category: Updating dependencies 💵 Funded on Issuehunt This issue has been funded on Issuehunt S-waiting-on-decision Status: Waiting on a go/no-go before implementing

Comments

@nayeemrmn
Copy link

nayeemrmn commented Apr 8, 2020

Issuehunt badges

Rust Version

rustc 1.42.0 (b8cedc004 2020-03-09)

Code

use clap;
use clap::Arg;
use clap::ArgGroup;

fn main() {
  let app = clap::App::new("hello")
    .bin_name("deno")
    .arg(
      Arg::with_name("option1")
        .long("option1")
        .takes_value(false),
    )
    .arg(
      Arg::with_name("pos1")
        .takes_value(true),
    )
    .group(
      ArgGroup::with_name("arg1")
        .args(&["pos1", "option1"])
        .required(true),
    )
    .arg(
      Arg::with_name("pos2")
        .takes_value(true)
    );

    match app.get_matches_from_safe(std::env::args().collect::<Vec<String>>()) {
      Ok(_) => (),
      Err(err) => err.exit(),
    }
}

Steps to reproduce the issue

cargo run -- -h

Affected Version of clap*

2.33.0

Actual Behavior Summary

USAGE:
    deno <pos1|--option1> [pos1]

cargo run -- --option1 abcd binds abcd to pos1 and complains about conflict between option1 and pos1

Blocks denoland/deno#4635, ref denoland/deno#4635 (comment).

Expected Behavior Summary

USAGE:
    deno <pos1|--option1> [pos2]

cargo run -- --option1 abcd should bind abcd to pos2. In other words, it should detect --option1 having been given as an option and accordingly skip pos1 which --option1 is grouped with.

--

If this is actually intended I welcome suggestions as to how I can get the above behaviour. But what else should be the semantics of grouping positional args with options?


IssueHunt Summary

Backers (Total: $20.00)

Become a backer now!

Or submit a pull request to get the deposits!

Tips

@nayeemrmn nayeemrmn added the C-bug Category: Updating dependencies label Apr 8, 2020
@pksunkara pksunkara added this to the 3.0 milestone Apr 9, 2020
@pksunkara
Copy link
Member

Yeah, this definitely looks like a bug. cc @CreepySkeleton

@CreepySkeleton
Copy link
Contributor

Yes. I would expect all of the following to be valid invocations:

app pos1 pos2
app --option1=val
app --option1 val pos2 <- THIS FAILS

Working on the fix, might take a while.

@nayeemrmn
Copy link
Author

app --option1=val
app --option1 val pos2 <- THIS FAILS

Small correction that --option1 doesn't take a value in my example but that would make for a better one.

bors bot added a commit that referenced this issue Apr 24, 2020
1856: Fix positional args in groups (#1794) r=pksunkara a=CreepySkeleton



Co-authored-by: CreepySkeleton <[email protected]>
@bors bors bot closed this as completed in #1856 Apr 24, 2020
@ldm0
Copy link
Member

ldm0 commented Nov 9, 2020

Should be reopen. The usage output is still not correct.

@pksunkara
Copy link
Member

@ldm0 You were saying that the original fix is wrong. Can you point out a test case why it is wrong?

@ldm0 ldm0 mentioned this issue Jan 17, 2021
@ldm0
Copy link
Member

ldm0 commented Jan 19, 2021

@pksunkara Check this:

let m = clap::App::new("hello")
    .bin_name("deno")
    .arg(Arg::new("pos1").takes_value(true))
    .arg(Arg::new("option2").long("option2").takes_value(false))
    .arg(Arg::new("pos2").takes_value(true))
    .group(
        ArgGroup::new("arg2")
            .args(&["pos2", "option2"])
            .required(true),
    )
    .try_get_matches();

dbg!(m);

with:

cargo run -- --option2 abcd

You will notice it complains about conflict between option2 and pos2. However the abcd should be placed in pos1.

@issuehunt-oss
Copy link

issuehunt-oss bot commented Jan 27, 2021

@pksunkara has funded $20.00 to this issue.


@issuehunt-oss issuehunt-oss bot added the 💵 Funded on Issuehunt This issue has been funded on Issuehunt label Jan 27, 2021
@dmaahs2017
Copy link

Is this issue solved?

@ldm0
Copy link
Member

ldm0 commented Feb 16, 2021

Is this issue solved?

Nope, currently.

@pksunkara
Copy link
Member

pksunkara commented May 26, 2021

@ldm0 Can we focus on finishing this first? Or is it blocked by other stuff?

@rami3l
Copy link
Contributor

rami3l commented Aug 5, 2021

@pksunkara @ldm0 The example above now passes on master with AppSettings::AllowMissingPositional:

/// Test for https://github.com/clap-rs/clap/issues/1794.
#[test]
fn positional_args_with_options() {
    let app = clap::App::new("hello")
        .bin_name("deno")
        .setting(AppSettings::AllowMissingPositional)
        .arg(Arg::new("option1").long("option1").takes_value(false))
        .arg(Arg::new("pos1").takes_value(true))
        .group(
            ArgGroup::new("arg1")
                .args(&["pos1", "option1"])
                .required(true),
        )
        .arg(Arg::new("pos2").takes_value(true));

    let m = app.get_matches_from(&["deno", "--option1", "abcd"]);
    assert!(m.is_present("option1"));
    assert!(!m.is_present("pos1"));
    assert!(m.is_present("pos2"));
    assert_eq!(m.value_of("pos2"), Some("abcd"));
}

And the usage is correctly shown as:

USAGE:
    deno <pos1|--option1> [pos2]

This AppSettings option is required due to the logic here, which indicates that a pos_counter bump is only available under certain conditions (which is needed for the example to work):

clap/src/parse/parser.rs

Lines 483 to 484 in 8b6034e

pos_counter = if low_index_mults || missing_pos {
let bump = if let Some(n) = remaining_args.get(0) {

Is this intended?

  • If so, I can make a PR to add this test and finally close this issue.
  • If not, maybe we should change the API to allow this special case of skipping certain pos args?

rami3l added a commit to rami3l/clap that referenced this issue Aug 5, 2021
@pksunkara
Copy link
Member

@ldm0 what do you think?

@ldm0
Copy link
Member

ldm0 commented Aug 7, 2021

This works:

use clap;
use clap::Arg;
use clap::ArgGroup;
use clap::AppSettings;

fn main() {
    let app = clap::App::new("hello")
        .bin_name("deno")
        .setting(AppSettings::AllowMissingPositional)
        .arg(Arg::new("option1").long("option1").takes_value(false))
        .arg(Arg::new("pos1").takes_value(true))
        .arg(Arg::new("pos2").takes_value(true))
        .group(
            ArgGroup::new("arg1")
                .args(&["pos1", "option1"])
                .required(true),
        )
        .get_matches();
}

But this is not:

use clap;
use clap::Arg;
use clap::ArgGroup;
use clap::AppSettings;

fn main() {
    let app = clap::App::new("hello")
        .bin_name("deno")
        .setting(AppSettings::AllowMissingPositional)
        .arg(Arg::new("option1").long("option1").takes_value(false))
        .arg(Arg::new("pos1").takes_value(true))
        .arg(Arg::new("pos2").takes_value(true))
        .arg(Arg::new("pos3").takes_value(true))
        .group(
            ArgGroup::new("arg1")
                .args(&["pos1", "pos2", "option1"])
                .required(true),
        )
        .get_matches();
}
error: The argument '--option1' cannot be used with '<pos1>'

USAGE:
    deno [pos3] <pos1|pos2|--option1>

For more information try --help

I don't think this issue can be closed now.

rami3l added a commit to rami3l/clap that referenced this issue Aug 11, 2021
@epage epage modified the milestones: 3.0, 3.1 Oct 16, 2021
@epage epage added A-help Area: documentation, including docs.rs, readme, examples, etc... E-medium Call for participation: Experience needed to fix: Medium / intermediate and removed C: args labels Dec 8, 2021
@epage epage added E-hard Call for participation: Experience needed to fix: Hard / a lot and removed E-medium Call for participation: Experience needed to fix: Medium / intermediate labels Apr 29, 2022
@epage
Copy link
Member

epage commented Apr 29, 2022

I'm torn on this. AllowMissingPositional is very limited solution. The right way of doing this is to check for conflicts for positionals and skip them. Right now, conflicts are only a validation concern and not a parse concern and I was hoping to keep it that way for our modularization work. However, being able to control when to skip positionals in this ways is also associated with our desire to make clap more flexible.

Going to put this back in the design/decision status and going to remove the milestone.

@epage epage removed this from the 3.x milestone Apr 29, 2022
@epage epage added S-waiting-on-decision Status: Waiting on a go/no-go before implementing and removed E-hard Call for participation: Experience needed to fix: Hard / a lot labels Apr 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-help Area: documentation, including docs.rs, readme, examples, etc... C-bug Category: Updating dependencies 💵 Funded on Issuehunt This issue has been funded on Issuehunt S-waiting-on-decision Status: Waiting on a go/no-go before implementing
Projects
None yet
7 participants