Marcelo Boeira
Marcelo Boeira

Reputation: 880

How can I prevent the last argument from needing to be quoted with clap?

I'm using clap and I get a unexpected behaviour when trying to parse arguments.

My command line tool is supposed to work like this

foo -u <user> <command>

e.g.:

foo -u jack echo s
foo -u paul ls -al

I need to get options such as user, but the <command> itself, I need to be the rest of the args.

The code below results in a behavior where I can't get the value of <command> unless it is quoted:

foo -u jack echo s
error: Found argument 's' which wasn't expected, or isn't valid in this context

Whereas this works fine:

foo -u jack 'echo s'

Is there any way of avoiding the quotes?

let matches = App::new("foo")
    .version("0.1")
    .arg(
        Arg::with_name("user")
            .short("u")
            .long("user")
            .required(true)
            .takes_value(true),
    )
    .arg(
        Arg::with_name("command")
            .help("The command to run")
            .required(true)
            .takes_value(true),
    )
    .get_matches();

I've also opened an issue on the clap repository.

Upvotes: 7

Views: 3935

Answers (2)

Sanjay T. Sharma
Sanjay T. Sharma

Reputation: 23228

As of the current clap version (3.2.12), the above code is no longer valid since the API has gone through some changes plus deprecations. For e.g.

  1. short now accepts a char instead of a &str
  2. with_name is now deprecated and replaced with new
  3. setting is now deprecated in favor of specific function names for each configuration
  4. multiple now replaced by multiple_values
  5. values_of deprecated in favor of get_many

The updated snippet is:

#[test]
fn test_parse_config() {
    let matches = App::new("foo")
        .version("0.1")
        .allow_hyphen_values(true)
        .trailing_var_arg(true)
        .arg(
            Arg::new("user")
                .short('u')
                .long("user")
                .required(true)
                .takes_value(true),
        )
        .arg(
            Arg::new("command")
                .help("The command to run")
                .required(true)
                .takes_value(true)
                .multiple_values(true)
        )
        // parse as if program ran as:   foo -u paul ls -al
        .get_matches_from(&["foo", "-u", "paul", "ls", "-al"]);

    let command: Vec<&str> = matches.get_many::<String>("command").unwrap().
        map(|s| s.as_str()).collect();
    println!("{:?}", command); // ["ls", "-al"]
}

Upvotes: 2

Freyja
Freyja

Reputation: 40894

By default, clap will only parse any argument once. This means that in -u jack echo s, it will parse -u jack as the "user" option, echo as the "command" argument, and have an argument s that it doesn't know what to do with (hence it "wasn't expected").

To retrieve all trailing arguments you need to set .multiple(true) on the last argument (in your case "command") so it parses all the remaining arguments. Additionally set the following options on the clap command to avoid parsing remaining arguments as clap arguments:

Here's an example:

let matches = App::new("foo")
    .version("0.1")
    .setting(clap::AppSettings::TrailingVarArg)
    .setting(clap::AppSettings::AllowLeadingHyphen)
    .arg(
        Arg::with_name("user")
            .short("u")
            .long("user")
            .required(true)
            .takes_value(true),
    )
    .arg(
        Arg::with_name("command")
            .help("The command to run")
            .required(true)
            .takes_value(true)
            .multiple(true),
    )
    // parse as if program ran as:   foo -u paul ls -al
    .get_matches_from(&["foo", "-u", "paul", "ls", "-al"]);

let command: Vec<&str> = matches.values_of("command").unwrap().collect();
println!("{:?}", command); // ["ls", "-al"]

Playground link

Upvotes: 10

Related Questions