-
I'm writing a program that can run with different "engines", with a set of common arguments and some engine-specific arguments, e.g. my ideal command line would be as follow:
I don't think that's quite possible as far as help goes, but I'd like to think I could get close behaviour-wise (perhaps with some post-checking...) I've got something very basic that allows expressing the switches with a sub struct for engine specific options, and I can then check arguments based on engine, but it's all very manual:
Here's code producing the above without grouping in help:
use clap::{Parser, Subcommand, ValueEnum};
use serde::{Deserialize, Serialize};
#[derive(Parser)]
#[clap(version, about)]
#[clap(propagate_version = true)]
#[clap(subcommand_required = true)]
#[clap(arg_required_else_help = true)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Parser, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
struct Engine1Opts {
/// boot this kernel instead of default one
#[clap(name = "eng1-opt", long, required = false)]
eng1: Option<String>,
}
#[derive(Parser, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
struct Engine2Opts {
/// boot this kernel instead of default one
#[clap(name = "eng2-opt", long, required = false)]
eng2: Option<String>,
}
#[derive(ValueEnum, Clone, Default, Debug)]
enum Engine {
#[default]
Auto,
Engine1,
Engine2,
Engine3,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Run thing
Run {
/// Template to run
template: String,
/// Some argument that works with all engines
#[clap(name = "common-arg", long)]
common_arg: bool,
/// Disable default modules
#[clap(name = "no-defaults", long)]
no_defaults: bool,
/// pick one engine
#[clap(name = "engine", long, default_value_t, value_enum)]
engine: Engine,
#[clap(flatten)]
Engine1Opts: Option<Engine1Opts>,
#[clap(flatten)]
Engine2Opts: Option<Engine2Opts>,
/// option common to eng1 and 2
#[clap(name = "eng12-opt", long)]
eng12_opt: bool,
},
}
fn main() {
let args = Cli::parse();
println!("Hello, {:?}", args.command);
} Note I also experimented with groups for the engine, but:
#[derive(Args, Clone, Default, Debug)]
#[group(required = false, multiple = false)]
struct Engine {
#[arg(long)]
engine1: bool,
#[arg(long)]
engine2: bool,
#[arg(long)]
engine3: bool,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Run thing
Run {
/// Template to run
template: String,
/// Disable default modules
#[clap(name = "no-defaults", long)]
no_defaults: bool,
/// pick one engine
#[clap(flatten)]
engine: Engine,
#[arg(long, requires = "engine1")]
eng1-opt: Option<String>,
},
} As things stand I'll likely continue with the first approach implementing tests manually even if it's a bit klumsy, but I'm thinking as far as clap goes this would be more natural with subcommands, e.g. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
Hmm, here's a variant with a nested subcommand... I had forgotten I had an "eat all" command parameter as well which makes things more annoying. I don't like two things here:
use clap::{Parser, Subcommand, ValueEnum};
use serde::{Deserialize, Serialize};
#[derive(Parser)]
#[clap(version, about)]
#[clap(propagate_version = true)]
#[clap(subcommand_required = true)]
#[clap(arg_required_else_help = true)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand, Clone, Debug)]
enum Engine {
Engine1 {
/// Template to run
template: Option<String>,
/// arguments
cmd: Vec<String>,
/// eng1 opt
#[clap(name = "eng1-opt", long)]
eng1: Option<String>,
/// eng12 opt
#[clap(name = "eng12-opt", long)]
eng12: Option<String>,
},
Engine2 {
/// eng2 opt
#[clap(name = "eng2-opt", long)]
eng2: Option<String>,
/// eng12 opt
#[clap(name = "eng12-opt", long)]
eng12: Option<String>,
},
Engine3,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Run thing
Run {
/// Template to run
template: Option<String>,
/// arguments
cmd: Vec<String>,
/// Some argument that works with all engines
#[clap(name = "common-arg", long)]
common_arg: bool,
/// Disable default modules
#[clap(name = "no-defaults", long)]
no_defaults: bool,
/// Hypervisor-specific options
#[clap(subcommand)]
engine: Option<Engine>,
},
}
fn main() {
let args = Cli::parse();
println!("Hello, {:?}", args.command);
} Well, this will be enough for today; if someone has any example where something similar to my initial "desired help" has been done that'd be appreciated! Thanks! |
Beta Was this translation helpful? Give feedback.
-
For help, what you want is either
Not at this time. You would at least need #4697 and #2621.
When we process arguments, we do it with an identifier which is currently only the field name. I might extend it to include the struct name but that won't help here. It would also still require the long flag to be unique. |
Beta Was this translation helpful? Give feedback.
For help, what you want is either
help_heading
ornext_help_heading
and you can divide them by engine.Not at this time. You would at least need #4697 and #2621.
When we process arguments, we do it with an identifier which is curre…