clap: Partially parse args, capturing unknown args into a vec, rather than error

Maintainer’s notes

Workaround

  • Parse with the args you care about with Command::ignore_errors

Affected Version of clap

  • 2.32.0

Bug or Feature Request Summary

It would be great to have a way to save unknow args into a Vec<> or slice. For example, an Arg::with_name() option, e.g. Arg::with_name('unknow').unknow_args() or something like this. Maybe it could be that instead of calling get_matches() on the arg list, add a get_know_matches() that returns a tuple, the first element being what would be get_matches() and the second a vector of the unknow args… Something like Python’s argparse:

args, unknown = parser.parse_known_args()
let (matches, unknow_matches) = App::new("myprog")
    .version("0.1")
    .author("You <you@example.com>")
    .about("Something about your program")
    .arg(Arg::with_name("debug")
        .short("d")
        .multiple(true)
        .help("Turn debugging information on"))
     .arg(Arg::with_name("quiet")
         .short("q")
         .long("shut-up")
         .help("Shut up"))
      .get_know_matches();

And then, if you call myprog -d --something, you have in matches the normal clap behaviour, and in unknow_matches a vector containg '--something'

I don’t know if I’m explaining it well, as English is not my primary language. EDIT: Save unknow in vector or slice instead of ignoring them

The reason for this is that i’m building a program that has “subargs” (e.g. myprogram -Xh is the message help for myprogram -X, but not the same help as myprogram -Yh or myprogram -h.) I can build this by adding -X and -Y arguments, and run another function based on which arg was used, but clap needs to know all arguments, and that’s why this would be nice.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 2
  • Comments: 30 (17 by maintainers)

Commits related to this issue

Most upvoted comments

I could still quite use this: I want to be able to pass-through the args to a specific subcommand to a child process, ignoring all the direct args to the subcommand itself.

I don’t think anything needs implementation here. As @cecton says, you can use those app settings to allow the end users to add extra arguments.

Hi @pksunkara, I’ve tried that, but this only ignores arguments “at the end”, not “in the middle”. Let me outline my situation:

What I want

Let’s assume I have following app:

use std::path::PathBuf;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct Opt {
    #[structopt(short = "-L", long, parse(from_os_str))]
    library_path: Vec<PathBuf>,

    #[structopt(short, long, parse(from_os_str))]
    output: PathBuf,

    #[structopt(short = "T", long)]
    script: Vec<String>,
}

fn main() {
    let opt: Opt = Opt::from_args();
    dbg!(opt);
}

Therefore my cli can be invoked like this:

$ cli -T memory.x -o odir/ -L ldir/ -T memory2.x
[src/main.rs:29] opt = Opt {
    library_path: [
        "ldir/",
    ],
    output: "odir/",
    script: [
        "memory2.x",
        "memory.x",
    ],
}

But now I also want to allow the user to add arbitrary arguments at all positions, like this (which is the short form of this):

$ cli \
    --eh-frame-hdr \
    -flavor gnu \
    -L ldir1/ \
    file.o \
    -o odir/ \
    --gc-sections \
    -L ldir2/ \
    -L ldir3/ \
    -Tmemory.x \
    -Bdynamic

In my software I only care about -L, -T, -o, but all the other arguments should be accepted anyways, since I want to pass them through to another cli. Tbh, I don’t necessarily need to capture them, but could also drop them, since currently I obtain all arguments to be passed further with fn std::env::args().

Therfore I’d like aboves invocation to result in sth like this:

[src/main.rs:29] opt = Opt {
    library_path: [
        "ldir1/",
        "ldir2/",
        "ldir3/",
    ],
    output: "dir/",
    script: [
        "memory.x",
    ],
    # `_rest` is nice to have, but actually not needed in my case
    _rest: [
        "--eh-frame-hdr",
        "-flavor",
        "gnu",
        "file.o",
        "--gc-sections",
        "-Bdynamic",
    ]
}

Why &[AppSettings::TrailingVarArg, AppSettings::AllowLeadingHyphen] doesn’t work

I’ve tried the solution suggested above, but this didn’t work for me for two reasons:

  1. It only accepts arguments added at the end, but not in the middle of the argument list
    • gets accept: cli -T memory.x -o odir/ -L ldir/ -T memory2.x -Bdynamic
    • get rejected: cli -T memory.x -Bdynamic -o odir/ -L ldir/ -T memory2.x
  2. Needed arguments after the first unknown argument don’t get captured anymore
    • If I invoke the cli like this: cli -T memory.x -o odir/ -L ldir/ -T memory2.x -Bdynamic -T memory3.x, the last -T memory3.x gets not added to script, but rather _rest.

After having written all of this down it sounds like a AppSettings::IgnoreAdditionalArgs. What do you think?

Hmm, I guess another question is what to do about unknown short arguments.

  • If any are unknown, do we treat the whole set as unknown and forward it?
  • Do we ignore unknown and artificially create the unknown argument to put into the captured positional?
    • How do we know what of the following might be known arguments vs captured values?
  • Do we just error on unknown shorts?

Earlier, I had posted some API design questions that first need to be resolved. Someone creating a proposal for that would be the first step.

We do have Command::ignore_errors for doing the workaround you mentioned.

@pksunkara I am not familiar with the clap parsing logic, but that is what I feared… Do you know some workaround or other crate which could help in my case by any chance?

You can ssk the user to provide the parameters for the inner process separately:

./my_program --my-argument --my-other-argument -- --inner-process-argument1 --inner-process-argument2

That’s what -- is usually for in Unix-like command lines.

I don’t think anything needs implementation here. As @cecton says, you can use those app settings to allow the end users to add extra arguments.

It would be useful for me too