I've been using Clap's derive
API but recently hit a case where I need some arguments' default to be determined at runtime.
A simplified version of my code is this, which is basically taken directly from
this documentation I found for how to combine the derive
-based and Builder-based APIs in Clap.
#[derive(clap::Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, default_value_t = {".".to_string()})]
repo: String,
// (... other args with static defaults)
#[command(flatten)]
runtime_default: RuntimeDefaultArgs,
}
struct RuntimeDefaultArgs {
result_db: PathBuf,
// (... other args with dynamic defaults)
}
impl FromArgMatches for RuntimeDefaultArgs {
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
let mut matches = matches.clone();
Self::from_arg_matches_mut(&mut matches)
}
fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result<Self, clap::Error> {
Ok(Self {
result_db: matches.get_one::<PathBuf>("result-db").unwrap().to_owned(),
})
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), clap::Error> {
let mut matches = matches.clone();
self.update_from_arg_matches_mut(&mut matches)
}
fn update_from_arg_matches_mut(&mut self, matches: &mut ArgMatches) -> Result<(), clap::Error> {
self.result_db = matches.get_one::<PathBuf>("result-db").unwrap().to_owned();
Ok(())
}
}
// This is where we actually define the arguments with dynamic defaults.
impl clap::Args for RuntimeDefaultArgs {
fn augment_args(cmd: clap::Command) -> clap::Command {
let default_db = calculate_default_db_path();
cmd.arg(
Arg::new("result-db")
.long("result-db")
.value_parser(value_parser!(PathBuf))
.default_value(default_db.to_str()),
)
}
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
Self::augment_args(cmd)
}
}
This is kinda OK for the current code, but now I want to add other subcommands, which will each have their own arguments with runtime-computed defaults. So it seems to follow this pattern I would need to implement these two repetitive traits again for each subcommand.
Is there anything I'm missing here in Clap that would make this easier? It seems this problem is not at all present in the builder API. This kinda makes sense, since the derive API is fundamentally about doing stuff "at compile time" so from that PoV think mixing the derive
and Builder APIs seems like the right direction. But maybe there's a more convenient way to do it?
The expression you pass to default_value_t
doesn't have to be a constant.
For any argument type that implements the required traits, including Display
, you could do this:
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser, Debug, Clone)]
pub struct Opts {
#[clap(long, default_value_t = default_result_db())]
pub result_db: String,
}
fn default_result_db() -> String {
todo!();
}
Since PathBuf
doesn't directly implement Display
, you could make a wrapper:
use std::ops::Deref;
use clap::Parser;
use std::fmt::{self, Debug, Display, Formatter};
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Parser, Debug, Clone)]
pub struct Opts {
#[clap(long, default_value_t = default_result_db())]
pub result_db: DisplayablePathBuf,
}
fn default_result_db() -> DisplayablePathBuf {
todo!();
}
#[derive(Clone)]
pub struct DisplayablePathBuf(PathBuf);
impl FromStr for DisplayablePathBuf {
type Err = <PathBuf as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
PathBuf::from_str(s).map(Self)
}
}
impl Debug for DisplayablePathBuf {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl Display for DisplayablePathBuf {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&self.0.display(), f)
}
}
impl Deref for DisplayablePathBuf {
type Target = PathBuf;
fn deref(&self) -> &PathBuf {
&self.0
}
}