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

Arg::multiple prior to a position variable eats the position variable as well #161

Closed
bbatha opened this issue Jul 16, 2015 · 6 comments
Closed
Assignees
Labels
C-bug Category: Updating dependencies

Comments

@bbatha
Copy link

bbatha commented Jul 16, 2015

I have this matches:

fn main() {
    let list_conflicts = vec!["inline", "command", "host", "host-set", "nossh-ping"];
    let matches = App::new("disburse")
                        .version(&crate_version!()[..])
                        .author("Ben Batha")
                        .arg(Arg::with_name("config")
                            .help("Location of the disburse config file")
                            .short("c")
                            .long("config")
                            .required(true)
                            .takes_value(true)
                            .empty_values(false)
                        )
                        .arg(Arg::with_name("inline")
                            .help("Prefix each host's output with its hostname")
                            .long("inline")
                            .short("i")
                        )
                        .arg(Arg::with_name("list")
                            .help("List all available targets")
                            .short("l")
                            .long("list")
                            .conflicts_with_all(&list_conflicts)
                        )
                        .arg(Arg::with_name("command")
                            .help("Command to run. May also come from STDIN")
                        )
                        .arg(Arg::with_name("host")
                            .help(concat!("Specify a host to run on. ",
                                  "Specify more than one host with a comma separated list"))
                            .long("host")
                            .takes_value(true)
                            .multiple(true)
                            .empty_values(false)
                            .conflicts_with("host-set")
                            .required(true)
                        )
                        .arg(Arg::with_name("host-set")
                            .help("Specify a named set of hosts to run on")
                            .long("host-set")
                            .takes_value(true)
                            .empty_values(false)
                            .conflicts_with("host")
                            .required(true)
                        )
                        .arg(Arg::with_name("representative")
                            .help("Pick a representative host to represent each host set")
                            .short("r")
                            .long("representative")
                            .requires("host-set")
                            .conflicts_with("host")
                        )
                        .arg(Arg::with_name("threads")
                             .help("Number of threads to execute on")
                             .long("num-threads")
                             .short("n")
                             .takes_value(true)
                             .empty_values(false)
                         )
                        .get_matches();
}

With the attempted run of:
"disburse --config hosts.json --inline --host host1 --host host2 -n 2 'sleep 10'"

matches.value_of("command") is populated however with:

"disburse --config hosts.json --inline --host host1 --host host2 'sleep 10'"

matches.value_of("command") is not populated.

@kbknapp
Copy link
Member

kbknapp commented Jul 16, 2015

This is because there isn't a way to determine when the multiple values stops and the next argument starts, unless you state a max or fixed number of values. Arg::multiple(true) by default says, "this argument can appear multiple times, OR have multiple values." So this is also a valid way to run your program:

$ disburse --config hosts.json --inline --host host1 host2 -- 'sleep 10'

There are a few ways to tackle your problem depending on your particular situation. If the argument that accepts multiple values has a fixed or maximum number of values you can specify that by setting Arg::number_of_values() or Arg::max_values() in conjunction with Arg::multiple(true). But that value must be > 1, which may or may not help you. But if for example you knew there always had to be exactly two hosts, and only two, you could set Arg::number_of_values(2) and your above invocation line would work fine.

The other more general option is to use -- and place all positional arguments after that.

$ disburse --config hosts.json --inline --host host1 --host host2 -- 'sleep 10'

OR

$ disburse --config hosts.json --inline --host host1 host2 -- 'sleep 10'

Because -- is somewhat of a unix standard and means, "everything that follows is a positional"

Hopefully this helps, if not please let me know :) And thanks for taking the time to file the issue! 👍

@kbknapp
Copy link
Member

kbknapp commented Jul 17, 2015

I also just noticed you are using concat!() but you can escape newlines to visually align help strings, or recently added in clap v1.1.0 you can use {n} inside help strings to insert newlines into help strings and ensure they're all still visually aligned when the user does a --help

Example:

// Arg with long help string which needs
// a new line printed to the user
Arg::with_name("long_help")
    .short("l")
    .help("This is a super long line that should be printed{n}one two lines to the user, yet still aligned nicely")

// Arg that I just visually want to put on two lines 
// in the source, yet printed as one line to the user
Arg::with_name("shorter")
    .short("s")
    .help("This is a kind of long line which I only want \
           on two lines in the source code.")

Assuming I had those in a proper program and ran --help it would look like this:

$ myprogram --help
myprogram v1.0
Does awesome things
Me, [email protected]

Usage:
    myprogram [FLAGS]

FLAGS:
    -h, --help    Displays this message
    -l            This is a super long line that should be printed
                  one two lines to the user, yet still aligned nicely
    -s            This is a kind of long line which I only want on two lines in the source code.

@bbatha
Copy link
Author

bbatha commented Jul 17, 2015

That's an excellent tip with the strings, thanks!

As for the argument problem. What I'd really like is to force it to be multiple option flags with a fixed number of inputs with something like required_inputs(1) to each one so this is legal:
--host foo --host bar
And this is illegal:
--host foo bar

Which should be sufficient to disambiguate between having multiple inputs for an option and positional arguments.

@kbknapp
Copy link
Member

kbknapp commented Jul 17, 2015

@bbatha

Actually I think you're right, and that just pointed out a logic bug in clap! So Arg::number_of_values() is supposed to allow a qty of >0, not >1. Once I fix this bug, add Arg::number_of_values(1) but keep Arg::multiple() for your host argument. This should have the exact effect you're looking for:

--host host1 --host host2 is legal
--host host1 host2 is illegal

I'll post back here once it's fixed.

@kbknapp kbknapp added C-bug Category: Updating dependencies D: easy and removed T: RFC / question labels Jul 17, 2015
@kbknapp kbknapp self-assigned this Jul 17, 2015
kbknapp added a commit that referenced this issue Jul 17, 2015
Allows setting `Arg::number_of_values(qty)` where `qty` < 2. This allows
things such as `Arg::number_of_values(1)` in conjuction with
`Arg::multiple(true)` which would make invoking the program like this
`myprog --opt val --opt val` legal, but `myprog --opt val1 val2`
illegal.

Closes #161
@kbknapp
Copy link
Member

kbknapp commented Jul 17, 2015

Once Travis passes #162 I'll merge into master and upload v1.1.2 to Crates.io

@kbknapp
Copy link
Member

kbknapp commented Jul 17, 2015

v1.1.2 on crates.io is now good to use :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: Updating dependencies
Projects
None yet
Development

No branches or pull requests

2 participants