4

I have two types of commands in my program, and I want to group one of the types in one enum. I don't want the user to have to type extra though, so I want to expose those commands at top level. How do I "flatten" subcommands?

Currently I'm working with the following (simplified) code

use clap::{Parser, Subcommand};

#[derive(Parser, Debug)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,
}


#[derive(Debug, Subcommand)]
#[non_exhaustive]
pub enum Commands {
    ChangeBrightness{ 
        #[command(subcommand)] 
        command: ChangeBrightnessCommand,
        #[arg(short, long)]
        devices: Vec<String>
    },
    List,
}

#[derive(Debug, Subcommand)]
pub enum ChangeBrightnessCommand {
    Set { 
        #[arg(value_parser = clap::value_parser!(u8).range(0..=100))] 
        percent: u8,
    },
    Get,
    // There are more (like increment, decrement, set-max, and set-min)
}

fn main() {
    let cli = Cli::parse();
    println!("{:?}", cli);
    match &cli.command {
        Commands::ChangeBrightness{command, devices} => todo!(),
        Commands::List => todo!()
    }
}

This is currently the output when I run it:

% target/debug/test-clap                  
Usage: test-clap <COMMAND>
Commands:
  change-brightness  
  list               
  help               Print this message or the help of the given subcommand(s)

% target/debug/test-clap change-brightness
Usage: test-clap change-brightness [OPTIONS] <COMMAND>

Commands:
  set   
  get   
  help  Print this message or the help of the given subcommand(s)

Options:
  -d, --devices <DEVICES>
  -h, --help               Print help

And this is a sketch of what I want the output to be:

% target/debug/test-clap                 
Usage: test-clap <COMMAND> [OPTIONS]
Commands:
  set
  get 
  list               
  help               Print this message or the help of the given subcommand(s)

Meaning that these should be all valid calls to the program:

% target/debug/test-clap set 100 --devices dev0 dev1 dev2
% target/debug/test-clap get -d dev0 dev1 dev2
% target/debug/test-clap set 5
% target/debug/test-clap get --devices dev0
% target/debug/test-clap list

I also tried #[command(flatten)], but I would get the following error:

error[E0277]: the trait bound `ChangeBrightnessCommand: clap::Args` is not satisfied
  --> src/main.rs:13:33
   |
13 |     ChangeBrightness{ #[command(flatten)] command: ChangeBrightnessCommand,#[arg(s...
   |                                 ^^^^^^^ the trait `clap::Args` is not implemented for `ChangeBrightnessCommand`
Typhaon
  • 828
  • 8
  • 27
  • I don't think this is possible. See [Clap issue #975](https://github.com/clap-rs/clap/issues/975). – Chayim Friedman Jul 20 '23 at 13:50
  • Is the reason to not put them all in `Commands` that you don't want to repeat `devices: Vec` for each variant? – BallpointBen Jul 20 '23 at 14:09
  • @BallpointBen Yes, that, and the fact that handling each of those commands basically starts the same, by retrieving the devices and filtering them based on their names. It feels like I'd repeat myself too much if I put everything in `Commands` – Typhaon Jul 20 '23 at 14:27

0 Answers0