Clap: repeatable struct / subcommand?

I'd like to allow for a repeatable group of arguments.

I've managed to get a single group of arguments to work using this:


#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,

    #[arg(short, long)]
    udp_target: String,

    #[arg(short = 'n', long)]
    hostname: String,

    #[arg(short, long)]
    skip_zone: Vec<u8>,
}

#[derive(Subcommand, Debug)]
enum Commands {
    /// define external command as additional datasource
    External(External),
}

#[derive(Args, Debug, Clone)]
struct External {
    zone: String,
    cmd: String,
    arg: Vec<String>,
}

But I'd like to have a possibility for multiple instances of that subcommand group.

My attempt at such a definition:

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[arg(short, long)]
    udp_target: String,

    #[arg(short = 'n', long)]
    hostname: String,

    #[arg(short, long)]
    skip_zone: Vec<u8>,

    #[arg(short, long)]
    external: Vec<External>,
}

#[derive(Args, Debug, Clone)]
struct External {
    zone: String,
    cmd: String,
    arg: Vec<String>,
}

Fails with this error:

error[E0599]: the method `value_parser` exists for reference `&&&&&&_AutoValueParser<External>`, but its trait bounds were not satisfied
    --> src/main.rs:26:5
     |
26   |     #[arg(short, long)]
     |     ^ method cannot be called on `&&&&&&_AutoValueParser<External>` due to unsatisfied trait bounds
...
37   | struct External {
     | ---------------
     | |
     | doesn't satisfy `External: From<&'s std::ffi::OsStr>`
     | doesn't satisfy `External: From<&'s str>`
     | doesn't satisfy `External: From<OsString>`
     | doesn't satisfy `External: From<std::string::String>`
     | doesn't satisfy `External: FromStr`
     | doesn't satisfy `External: ValueEnum`
     | doesn't satisfy `External: ValueParserFactory`
     |
    ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-4.0.23/src/builder/value_parser.rs:2171:1
     |
2171 | pub struct _AutoValueParser<T>(std::marker::PhantomData<T>);
     | ------------------------------ doesn't satisfy `_: clap::builder::via_prelude::_ValueParserViaParse`
     |
     = note: the following trait bounds were not satisfied:
             `External: ValueEnum`
             which is required by `&&&&&_AutoValueParser<External>: clap::builder::via_prelude::_ValueParserViaValueEnum`
             `External: ValueParserFactory`
             which is required by `&&&&&&_AutoValueParser<External>: clap::builder::via_prelude::_ValueParserViaFactory`
             `External: From<OsString>`
             which is required by `&&&&_AutoValueParser<External>: clap::builder::via_prelude::_ValueParserViaFromOsString`
             `External: From<&'s std::ffi::OsStr>`
             which is required by `&&&_AutoValueParser<External>: clap::builder::via_prelude::_ValueParserViaFromOsStr`
             `External: From<std::string::String>`
             which is required by `&&_AutoValueParser<External>: clap::builder::via_prelude::_ValueParserViaFromString`
             `External: From<&'s str>`
             which is required by `&_AutoValueParser<External>: clap::builder::via_prelude::_ValueParserViaFromStr`
             `External: FromStr`
             which is required by `_AutoValueParser<External>: clap::builder::via_prelude::_ValueParserViaParse`
note: the following traits must be implemented
    --> /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/str/traits.rs:543:1
     |
543  | pub trait FromStr: Sized {
     | ^^^^^^^^^^^^^^^^^^^^^^^^
     |
    ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-4.0.23/src/builder/value_parser.rs:2074:1
     |
2074 | pub trait ValueParserFactory {
     | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
    ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-4.0.23/src/derive.rs:374:1
     |
374  | pub trait ValueEnum: Sized + Clone {
     | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
    ::: /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/convert/mod.rs:371:1
     |
371  | pub trait From<T>: Sized {
     | ^^^^^^^^^^^^^^^^^^^^^^^^
     = note: this error originates in the macro `clap::value_parser` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0599`.
error: could not compile `rust_playground_2022-11-07` due to 2 previous errors

Okey so I've found a solution, though I don't particularly like it, since doing it like that means I have to parse the struct from a &str myself:

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[arg(short, long)]
    external: Vec<External>,
}

#[derive(Debug, Clone)]
struct External {
    zone: String,
    cmd: String,
    args: Vec<String>,
}

// https://doc.rust-lang.org/std/str/trait.FromStr.html
impl FromStr for External {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(";").collect();
        
        Ok(External {
            zone: String::from(parts[0]),
            cmd: String::from(parts[1]),
            args: parts[2..].iter().map(|x| String::from(*x)).collect(),
        })
    }
}

And I don't have any good ideas on how to expand that struct's parsing by a few optional fields I'd like it to have...

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.