我一直在使用 Clap 的
derive
API,但最近遇到了一个情况,我需要在运行时确定一些参数的默认值。
我的代码的简化版本是这样的,基本上直接取自
我找到了这个文档,了解如何在 Clap 中组合基于 derive
和基于 Builder 的 API。
#[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)
}
}
这对于当前代码来说还不错,但现在我想添加其他子命令,每个子命令都有自己的参数和运行时计算的默认值。所以它似乎遵循这种模式,我需要为每个子命令再次实现这两个重复的特征。我在 Clap 中缺少什么东西可以让这件事变得更容易吗?看来这个问题根本不存在于构建器 API 中。这是有道理的,因为派生 API 从根本上讲是在“编译时”做一些事情,因此从 PoV 来看,混合
derive
和 Builder API 似乎是正确的方向。但也许有更方便的方法来做到这一点?
Display
,您可以这样做:
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!();
}
由于PathBuf
不直接实现
Display
,你可以制作一个包装器:
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
}
}