Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Clap – subcommands with possibly shared sets of default values?

I’ve been going in circles on this for a while and haven’t found a good solution:

I’ve got a bunch of simulations in the same codebase. I’m trying to be an adult and use command line arguments to pick which simulation runs and with what parameters. The issue is that lots of the arguments are shared between different simulations, and some sets are passed to shared components.

For instance, in the below I have three simulations, two of which share the "Threaded" argument subset, another two share the MazeCli argument subset, which would be wanted by the Maze trait. (In the full thing, there are more arguments and combinations.)

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

use clap::*;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    sim: Simulation,
}

#[derive(Subcommand, Debug)]
enum Simulation {
    Maze {
        maze_args: MazeCli,
        thread_args: ThreadCli,
    },
    ThreadedMaze {
        maze_args: MazeCli,
    },
    Threaded {
        thread_args: ThreadCli,
    },
}

trait Maze {
    fn new(args: MazeCli) -> Self;
}

#[derive(Args, Debug, Clone)]
struct MazeCli {
    squares: usize,
    openness: f64,
}

#[derive(Args, Debug, Clone)]
struct ThreadCli {
    threads: usize,
}
fn main() {
    let config = Cli::parse();
    println!("{:?}", config);
}

That version fails because it wants the MazeCli and ThreadCli to implement ValueEnum (as far as I can tell – the actual error is long and unhelpful). ValueEnum can’t be derived for structs, though. I’ve tried a few other approaches, but have not gotten anything to compile.

I could do everything as one flat arg list, but then error messages won’t tell you what’s expected for the specific subcommand you run.

I could manually specify every list, but that’s a bunch of boilerplate, especially when default values are factored in.

What’s the right way to do this? It it something clap supports at all?

Bonus questions :

  • If this is possible at all, is there any way for specific subcommands to override generic defaults for their specific case (like, if one simulation wanted twice as many threads as the norm by default.)
  • Can I make the default values somehow fall back to a Default trait implementation for that struct?

>Solution :

Use #[command(flatten)]:

#[derive(Subcommand, Debug)]
enum Simulation {
    Maze {
        #[command(flatten)]
        maze_args: MazeCli,
        #[command(flatten)]
        thread_args: ThreadCli,
    },
    ThreadedMaze {
        #[command(flatten)]
        maze_args: MazeCli,
    },
    Threaded {
        #[command(flatten)]
        thread_args: ThreadCli,
    },
}

Can I make the default values somehow fall back to a Default trait implementation for that struct?

Use default_value_t without a parameter. It defaults to Default::default():

#[derive(Args, Debug, Clone)]
struct MazeCli {
    #[arg(long, default_value_t)]
    squares: usize, // Will be optional with a default of 0
    openness: f64,
}

If this is possible at all, is there any way for specific subcommands to override generic defaults for their specific case (like, if one simulation wanted twice as many threads as the norm by default.)

I don’t think it’s possible without using separate structs.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading