I am using Clap (derive) v4.x for a Rust CLI application. The basics are working properly for command line arguments, environment variable values, and default values.
Next, for each match, I want to determine the ValueSource (default, envvariable, command line), but I cannot figure out how to get this information using a derived subcommand (i.e., my ContainerCommand in the code, below). I hope someone can give me a hint or a nudge in the right direction.
I need to implement hierarchical evaluation of the potential configuration data for the app: cmdline > envvar > config file > defaults.
My goal is to deserialize config information from a toml file. Copy the data from clap into a Config struct. For any items in the Config struct that are None or came from Defaults, I will replace the value with what is found from the toml file's data, if it exists. Otherwise, the default value will be set.
I know that there are excellent packages (Figment, Config-rs, Twelf, …) that implement hierarchical configuration processing, but each one of them offers unique challenges/issues when I've tried to use them in our application. Hence, I want to use the simple solution that I've described.
Here is the strawman app that I am using to figure things out:
/// This is the entry point for the application.
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = App::parse();
dbg!(&app);
match &app.command {
Command::Container(c) => {
//let matches = ContainerCommand::command().get_matches();
// if matches.contains_id("name") {
// println!("{:?}", c.name);
// match matches.value_source("name").unwrap() {
// ValueSource::DefaultValue => { println!("Is the default value")}
// ValueSource::EnvVariable => { println!("Is the envvar value")}
// ValueSource::CommandLine => { println!("Is the commandline value")}
// _ => { println!("Unknown source for the value...")}
// }
// }
}
}
Ok(())
}
/// The definition of the command line and its arguments
#[derive(Debug, Parser, Serialize)]
#[command(author, version, about, long_about = None, next_line_help = true, propagate_version = true)]
pub struct App {
#[command(flatten)]
global_opts: GlobalOpts,
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Args, Serialize)]
struct GlobalOpts {
/// config_path is the path to a configuration (.toml) file, which defaults to the current directory.
#[arg(short = 'c', long, global = true, default_value = "config.toml")]
config_path: std::path::PathBuf,
}
#[derive(Debug, Subcommand, Deserialize, Serialize)]
enum Command {
#[command(about = "The hash container command determines the base64 binary MD5 hash for each blob in a container.", long_about = None)]
/// Determines the base64 binary MD5 hash for each blob in a container
Container(ContainerCommand),
}
/// The definition of the command line and its arguments
#[derive(Parser, Debug, Deserialize, Serialize)]
struct ContainerCommand {
/// The name of a client
#[arg(short = 'n', long, env("NAME"), default_value = "kdev")]
name: Option<String>,
}
You can't use parse
as that already does all the parsing and throws away the information you need. Instead you have to use App::command().get_matches()
:
use clap::CommandFactory;
let matches = App::command().get_matches_from(["self", "-c", "foo", "container"]);
if let Some((_subcommand_name, subcommand_matches)) = matches.subcommand() {
match subcommand_matches.value_source("name") {
Some(ValueSource::DefaultValue) => println!("Is the default value"),
Some(ValueSource::EnvVariable) => println!("Is the envvar value"),
Some(ValueSource::CommandLine) => println!("Is the commandline value"),
Some(_) => println!("Unknown source for the value..."),
None => (),
}
}
and you can later get the App
using FromArgMatches
:
use clap::FromArgMatches;
let app = App::from_arg_matches(&matches).unwrap();