Search code examples
rustclap

Using a structure as a command line argument in clap


Trying to use a struct within a struct in clap:

use clap::{Args, Parser};
use std::path::PathBuf;

#[derive(Parser, Debug)]
enum Command {
    Foo(Foo),
}

#[derive(Args, Debug)]
struct Foo {
    bar: Option<Bar>,

    path: PathBuf,
}

#[derive(Parser, Clone, Debug)]
struct Bar {
    bla: u8,

    bla_2: String,
}

fn main() {
    let cli = Command::parse();
    println!("cli {:#?}", cli);
}

So I could call the app with the following options: cargo run -- foo bar 42 baz /tmp/a or just cargo run -- foo /tmp/a since the bar argument is optional.

However, currently it does not build:

  --> src/main.rs:11:5
   |
11 |     bar: Option<Bar>,
   |     ^^^ the trait `FromStr` is not implemented for `Bar`
   |

And since the values within Bar have to be space-separated implementing a FromStr would not do the trick anyway.

Is it even possible to do something of this fashion in clap currently?


Solution

  • There are several problems with your code. The biggest one is:

    • An optional positional item can never come before a required positional argument

    This is a problem in your case because your command line looks like this:

    cargo run -- <required> [optional] /tmp/a
    

    If you have a required path at the end, there can not be an optional positional argument before that.

    Further problems:

    • #[derive(Parser)] should be attached to a struct, not an enum.
    • There should only be one #[derive(Parser)], which represents the entry object of your arguments parser.

    I'm unsure how else to help you, except pointing out your problems. If the invocations cargo run -- foo bar 42 baz /tmp/a and cargo run -- foo /tmp/a are non-negotiable, I don't think clap is the right library for you; I think you should parse by hand.