#[clap(long = "type", num_args = 0.., default_values_t = [MyType::a], value_parser = MyType::from_string)]
pub name_types: Vec<MyType>,
#[clap(long = "name", num_args = 1..)]
pub names: Vec<String>
我有一个类似的代码(MyType 是一个具有两个可能值的枚举,假设“a”和“b”)。我可以调用我的 cli:
my-cli --name andrew --type a --name billy --type b
,它将创建名称为 ["andrew","billy"]
和 name_types 为 [MyType::a, MyType::b]
。但我想将其称为 my-cli --name andrew --name billy --type b
并期望它也能正常工作(类型的默认值,如果它没有预设为 MyType::a
)。另外,我希望它保持顺序,即如果我将 cli 称为 my-cli --name andrew --name billy --type b --name carol
=> name_types = [a,b,a]
据我所知,您隐含地期望
names
和 name_types
的元素之间存在一对一的关系。好吧,你还没有告诉 clap 这件事,clap
也不适合指定这种不平凡的逻辑关系 - 这不是命令行解析器的工作。
相反,最好的方法可能是让你的
clap
东西保持简单。然后将自定义逻辑写入“后处理”clap
的输出到正确编码逻辑关系的结构中,并在核心应用程序中使用它。
这是我要写的:
use clap::{CommandFactory, FromArgMatches, Parser, ValueEnum};
// ValueEnum is probably the better way to do this
// unless your type is actually more complicated than in this example
#[derive(Debug, Copy, Clone, ValueEnum)]
enum MyType {
A,
B,
}
#[derive(Debug, Parser)]
struct Cli {
// #[clap(...)] is deprecated
#[arg(long = "type")]
pub name_types: Vec<MyType>,
// num_args doesn't do what you probably think it does
// see https://github.com/clap-rs/clap/issues/4507#issuecomment-1372250231
#[arg(long = "name", required = true)]
pub names: Vec<String>,
}
// this type properly expresses the logical relation
// "each name must have an associated type"
#[derive(Debug)]
struct User {
name: String,
name_type: MyType,
}
fn main() {
let matches = Cli::command().get_matches();
let Cli { name_types, names } = Cli::from_arg_matches(&matches).unwrap();
// get indices of `names`
let name_indices = matches
.indices_of("names")
.into_iter()
.flatten()
.collect::<Vec<_>>();
// get the applicable index range of the `--type` option for each name
// the `--type` options found between a `--name` and the next is applicable
let applicable_type_index_ranges = {
let mut window_indices = name_indices.clone();
// for the last `--name` option, the upper index is unbounded
window_indices.push(std::usize::MAX);
window_indices
.windows(2)
.map(|window| match window {
[start, end] => (*start, *end),
_ => unreachable!(),
})
.collect::<Vec<_>>()
};
assert_eq!(names.len(), applicable_type_index_ranges.len());
// pair the `--type` options with their indices
let name_types_with_indices = {
let type_indices = matches
.indices_of("name_types")
.into_iter()
.flatten()
.collect::<Vec<_>>();
assert_eq!(name_types.len(), type_indices.len());
name_types.into_iter().zip(type_indices).collect::<Vec<_>>()
};
// construct `User`s with types properly assigned
let users = names
.into_iter()
.zip(applicable_type_index_ranges)
.map(|(name, (start_idx, end_idx))| {
let name_type = name_types_with_indices
.iter()
// only `--type` options within the range are applicable
.filter_map(|&(t, idx)| (idx > start_idx && idx < end_idx).then_some(t))
// assuming you want the last applicable `--type` to take precedence
.last()
// your default value if no `--type` is present
.unwrap_or(MyType::A);
User { name, name_type }
})
.collect::<Vec<_>>();
dbg!(users);
}
$ cargo run -- --name romeo --type b --name juliett
[src/main.rs:86:5] users = [
User {
name: "romeo",
name_type: B,
},
User {
name: "juliett",
name_type: A,
},
]
如您所见,这是可以完成的,但它并不简单,在很多地方,
clap
为您做出固执己见的设计决策会很奇怪。