#[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>
I have a similar code (MyType is a enum with two possible values, assume "a" and "b"). I can call my cli:
my-cli --name andrew --type a --name billy --type b
and it will create names as ["andrew","billy"]
and name_types as [MyType::a, MyType::b]
. But I wanna call it as my-cli --name andrew --name billy --type b
and expect it to work as well (default value for type, if it's not preset to be MyType::a
). Also, i want it to keep the order, i.e. if i call cli as my-cli --name andrew --name billy --type b --name carol
=> name_types = [a,b,a]
From what I can tell, you are implicitly expecting a 1-to-1 relation between the elements of names
and of name_types
. Well, you haven't told clap about it, nor is clap
suitable for specifying this kind of non-trivial logical relations - that's not the job of a command line parser.
Instead, probably the best way to do this is to keep your clap
stuff simple. Then write custom logic to "post-process" clap
's output into a structure that properly encodes the logical relation, and use that in your core application.
Here's what I would write. Bear in mind I personally prefer a declarative and functional style, but this could be just as well written imperatively.
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 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 = {
// get indices of `names`
let mut name_indices = matches
.indices_of("names")
.into_iter()
.flatten()
.collect::<Vec<_>>();
// for the last `--name` option, the upper index is unbounded
name_indices.push(std::usize::MAX);
name_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,
},
]
As you can see, it can be done but it's non-trivial, with plenty of places where it would be weird for clap
to make an opinionated design decision for you.