Search code examples
rustclap

How to use global option when subcommands are defined when using Clap in Rust?


So I am trying to create a command line application with subcommands. The only problem now is I can't seem to get the main command to work after the subcommands has been defined.

What I have thus far is this:

use clap::{Parser, Subcommand, Args};

#[derive(Parser)]
#[command(about="Hello world")]
pub struct Cli {
    #[clap(long,short)]
    pub name: Option<String>,
    #[command(flatten)]
    pub date: DateShared,
    #[command(subcommand)]
    pub subcommands: AppSubCommands,
}

#[derive(Subcommand)]
pub enum AppSubCommands {
    #[command(about="Address info",name="address")]
    AddressCommands(Address)
}

#[derive(Args, Debug)]
pub struct Address {
    #[command(flatten)]
    pub date: DateShared,
    #[arg(long, short)]
    pub street: Option<String>,
    #[arg(long, short)]
    pub number: Option<String>

}

#[derive(Args, Debug)]
pub struct DateShared {
    #[arg(long,short)]
    pub month: Option<String>,
    #[arg(long,short)]
    pub day: Option<String>,
}

This compiles fine. And even when I run --help it shows this

     Running `target/debug/repl --help`
Hello world

Usage: repl [OPTIONS] <COMMAND>

Commands:
  address  Address info
  help     Print this message or the help of the given subcommand(s)

Options:
  -n, --name <NAME>    
  -m, --month <MONTH>  
  -d, --day <DAY>      
  -h, --help           Print help information

Which shows the options available on the global level and that there is an address subcommand.

But when I try to run using the global options it fails at runtime. For example

fn main() {
    let cli = Cli::parse();
    println!("name {:?}", cli.name.unwrap());
}

fails with the following

     Running `target/debug/repl --name finlay`
error: 'repl' requires a subcommand but one was not provided
  [subcommands: address, help]

Usage: repl [OPTIONS] <COMMAND>

For more information try '--help'

But running the subcommand works

fn main() {
    let cli = Cli::parse();
    match cli.subcommands {
        AppSubCommands::AddressCommands(address) => println!("{}", address.date.month.unwrap()),
        _ => println!("Also all good")
    }
}

and I can run with cargo run -- address --month jan so the subcommand works.

So how do I have the subcommand working while also being able to run the main/parent command?


Solution

  • You wrap optional parts of your command line interface in Option including optional commands.

    #[derive(Parser)]
    #[command(about="Hello world")]
    pub struct Cli {
        #[clap(long,short)]
        pub name: Option<String>,
        #[command(flatten)]
        pub date: DateShared,
        #[command(subcommand)]
        pub subcommands: Option<AppSubCommands>,
    }