You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This does pop up from time to time and the answer seems to be generally "trailing_var_arg + allow_hyphen_values", but this has various unexpected failure modes. Summary of the tests below:
Regular positional arg (e.g. binary name) followed by var_arg (e.g. options for the binary) fails like this:
"--help" and similar are actually picked up inside the var arg. Seems like a bug to me.
Regular options are some times ignored (-xfoo), sometimes picked up (-xfoo). Seems like a bug to me.
Single var_arg for everything fails like this
Short options are not parsed, -xfoo is parsed as an option, while -xfoo starts the var_arg. Seems like a bug to me.
Single var_arg with last = true appears to be the only option that reliably works, but has the following drawbacks:
Requires use of --, even though the resulting parser isn't generally ambigiuous under getopt/getopt_long rules (any positional terminates parsing of options and flags).
Precludes splitting the argument list into two parts
This use case seems common enough that there should be one preferred and documented way to do it which does not fail in strange ways. Most of the odd behavior here seems to come from clap's decision to allow mixing positional arguments with options and flags (much like argparse does, though clap seems to have fewer bugs than argparse). I've looked around and haven't seen anything to disable this (ime undesirable) behavior.
use clap::Parser;fnmain(){// clap v4.4.18// clap_derive v4.4.7#[derive(Parser,Debug)]pubstructExecCommand{#[arg(long, short = 'x')]some_option:Option<String>,/// Command to run#[arg(required = true)]exec:String,/// Arguments to the command#[arg(required = false, trailing_var_arg = true, allow_hyphen_values = true)]args:Vec<String>,}//println!("{:#?}", ExecCommand::parse_from(["test", "/usr/bin/cp", "-h"]));// prints:// Usage: test [OPTIONS] <EXEC> [ARGS]...//// Arguments:// <EXEC> Command to run// [ARGS]... Arguments to the command//// Options:// -v, --verbose// -x, --some-option <SOME_OPTION>// -h, --helpprintln!("{:#?}",ExecCommand::parse_from(["test","-xfoo","/usr/bin/cp","arg1","arg2"]));// ExecCommand {// some_option: Some(// "foo",// ),// exec: "/usr/bin/cp",// args: [// "arg1",// "arg2",// ],// }println!("{:#?}",ExecCommand::parse_from(["test","/usr/bin/cp","-xfoo","arg1","arg2"]));// Weirdly enough this works:// ExecCommand {// some_option: None,// exec: "/usr/bin/cp",// args: [// "-xfoo", // <- option ignored// "arg1",// "arg2",// ],// }println!("{:#?}",ExecCommand::parse_from(["test","/usr/bin/cp","-x","foo","arg1","arg2"]));// ... unless you use a space.// ExecCommand {// some_option: Some( // <- whoopsie// "foo",// ),// exec: "/usr/bin/cp",// args: [// "arg1",// "arg2",// ],// }#[derive(Parser,Debug)]pubstructExecCommandOneArg{#[arg(long, short = 'x')]some_option:Option<String>,/// Command with arguments#[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]args:Vec<String>,}println!("{:#?}",ExecCommandOneArg::parse_from(["test","/usr/bin/cp","-h"]));// ExecCommandOneArg {// some_option: None,// args: [// "/usr/bin/cp",// "-h",// ],// }// but watch thisprintln!("{:#?}",ExecCommandOneArg::parse_from(["test","-xfoo","/usr/bin/cp","arg1","arg2"]));// ExecCommandOneArg {// some_option: None,// args: [// "-xfoo", // <-- whoops!// "/usr/bin/cp",// "arg1",// "arg2",// ],// }println!("{:#?}",ExecCommandOneArg::parse_from(["test","-x","foo","/usr/bin/cp","arg1","arg2"]));// must suddenly use spaced form// ExecCommandOneArg {// some_option: Some(// "foo",// ),// args: [// "/usr/bin/cp",// "arg1",// "arg2",// ],// }#[derive(Parser,Debug)]pubstructExecCommandLast{#[arg(long, short = 'x')]some_option:Option<String>,/// Command to run#[arg(required = true, last = true)]exec:String,/// Arguments to the command#[arg(required = false, trailing_var_arg = true, allow_hyphen_values = true)]args:Vec<String>,}// println!("{:#?}", ExecCommandLast::parse_from(["test", "-xfoo", "/usr/bin/cp", "arg1", "arg2"]));// error: unexpected argument '/usr/bin/cp' found//// Usage: test [OPTIONS] -- <EXEC> [ARGS]...//// For more information, try '--help'.//// Fair enough, the docs say this, though I would've expected this to mean something more similar// getopt-style parsing, where the first argument with this flag terminates parsing of options/flags// println!("{:#?}", ExecCommandLast::parse_from(["test", "-xfoo", "--", "/usr/bin/cp", "arg1", "arg2"]));// Except it doesn't seem to work:// error: the following required arguments were not provided:// <EXEC>//// Usage: test --some-option <SOME_OPTION> -- <EXEC> <ARGS>...//// For more information, try '--help'.// println!("{:#?}", ExecCommandLast::parse_from(["test", "--", "/usr/bin/cp", "arg1", "arg2"]));// nope// error: the following required arguments were not provided:// <EXEC>//// Usage: test -- <EXEC> <ARGS>...//// For more information, try '--help'.// I think the problem here is that last=true cannot be followed by another argument, so in// clap's model "exec" is now the physically last argument, but since it's then preceded by// "args", "args" sucks up all the arguments and "exec" gets nothing.#[derive(Parser,Debug)]pubstructExecCommandOneArgLast{#[arg(long, short = 'x')]some_option:Option<String>,/// Command to run#[arg(required = true, last = true)]args:Vec<String>,}// println!("{:#?}", ExecCommandOneArgLast::parse_from(["test", "-xfoo", "/usr/bin/cp", "arg1", "arg2"]));// error: unexpected argument '/usr/bin/cp' found//// Usage: test [OPTIONS] -- <ARGS>...//// For more information, try '--help'.println!("{:#?}",ExecCommandOneArgLast::parse_from(["test","-xfoo","--","/usr/bin/cp","arg1","arg2"]));// ExecCommandOneArgLast {// some_option: Some(// "foo",// ),// args: [// "/usr/bin/cp",// "arg1",// "arg2",// ],// }println!("{:#?}",ExecCommandOneArgLast::parse_from(["test","--","/usr/bin/cp","-h"]));// ExecCommandOneArgLast {// some_option: None,// args: [// "/usr/bin/cp",// "-h",// ],// }}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
This does pop up from time to time and the answer seems to be generally "trailing_var_arg + allow_hyphen_values", but this has various unexpected failure modes. Summary of the tests below:
Regular positional arg (e.g. binary name) followed by var_arg (e.g. options for the binary) fails like this:
-xfoo
), sometimes picked up (-x
foo
). Seems like a bug to me.Single var_arg for everything fails like this
-x
foo
is parsed as an option, while-xfoo
starts the var_arg. Seems like a bug to me.Single var_arg with
last = true
appears to be the only option that reliably works, but has the following drawbacks:--
, even though the resulting parser isn't generally ambigiuous under getopt/getopt_long rules (any positional terminates parsing of options and flags).This use case seems common enough that there should be one preferred and documented way to do it which does not fail in strange ways. Most of the odd behavior here seems to come from clap's decision to allow mixing positional arguments with options and flags (much like argparse does, though clap seems to have fewer bugs than argparse). I've looked around and haven't seen anything to disable this (ime undesirable) behavior.
Beta Was this translation helpful? Give feedback.
All reactions