Hi,
When using clap, I would like to have an argument that is of a fixed array size:
e.g
struct Args {
#[arg(
long = "stages",
help = "Comma-separated list of stages to run",
default_value = ["clear","configure","build","install"],
)]
stages: [&str; 4]
}
However, I keep getting the following error and am not quite sure why this won't work. Any help is appreciated.
error[E0277]: the trait bound `clap::builder::OsStr: From<[&str; 4]>` is not satisfied
--> src/cli_args.rs:21:25
|
21 | default_value = ["clear","configure","build","install"],
| ------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[&str; 4]>` is not implemented for `clap::builder::OsStr`
| |
| required by a bound introduced by this call
If you want it to be comma-separated like that, you'll have to write your own ValueParser function. It would look something like this (Rust Playground):
use clap::{builder::ValueParser, Parser};
fn parse_stages(arg: &str) -> Result<[String; 4], &'static str> {
arg.split(',')
.map(str::to_string)
.collect::<Vec<_>>()
.try_into()
.map_err(|_| "must specify 4 stages")
}
#[derive(Parser)]
struct Args {
#[arg(
long = "stages",
help = "Comma-separated list of stages to run",
default_value = "clear,configure,build,install",
value_parser = ValueParser::new(parse_stages),
)]
stages: [String; 4],
}
fn main() {
let args = Args::parse_from(["example", "--stages=a,b,c,d"]);
println!("{:?}", args.stages);
let args = Args::parse_from(["example"]);
println!("{:?}", args.stages);
let args = Args::parse_from(["example", "--stages=a,b,c,d,e"]);
println!("{:?}", args.stages);
}
Naive question 1: how can I change parse_stages to accept If the length of stages is not exactly 4?
for example, the stages could only have 1 entry ( e.g --stages=clear ) and that should be still valid.
Naive question 2: how can I change this so the "clear","configure","build","install" are only the valid keyword entries that stages could have?
This is a bit tricky, since if parse_stages returns a Vec<String>, then Clap will incorrectly assume that you're trying to get multiple arguments. So we have to trick Clap a bit by returning a Box<[String]> instead. If you don't plan on adding or removing any elements, then using it shouldn't be any different from a Vec. Rust Playground:
use clap::{builder::ValueParser, Parser};
fn parse_stages(arg: &str) -> Result<Box<[String]>, String> {
arg.split(',')
.map(|stage| {
if ["clear", "configure", "build", "install"].contains(&stage) {
Ok(stage.to_string())
} else {
Err(format!("unknown stage '{stage}'"))
}
})
.collect()
}
#[derive(Parser)]
struct Args {
#[arg(
long = "stages",
help = "Comma-separated list of stages to run",
value_parser = ValueParser::new(parse_stages),
default_value = "clear,configure,build,install",
)]
stages: Box<[String]>,
}
fn main() {
let args = Args::parse_from(["example", "--stages=clear,configure"]);
println!("{:?}", args.stages);
let args = Args::parse_from(["example"]);
println!("{:?}", args.stages);
let args = Args::parse_from(["example", "--stages=clear,test"]);
println!("{:?}", args.stages);
}
(Also, note that this won't accept an empty list of stages, since it will interpret it as a single empty string. To change that, you could just check for an empty string before splitting.)