[英]Getting relative order of different command line options using clap & structopt
我有一個帶有不同選項的命令,這些選項的相對順序對命令的語義很重要。 例如,在command --config A --some-option --config-file B --random-option --config C --another-option --more-options --config-file D
中,相對順序A, B, C, D
很重要,因為它會影響命令的含義。
如果我只是定義選項如下:
#[derive(Debug, StructOpt)]
pub struct Command {
#[structopt(long = "config")]
configs: Vec<String>,
#[structopt(long = "config-file")]
config_files: Vec<String>,
}
然后我會得到兩個向量, configs = [A, C]
和config_files = [B, D]
但是configs
和config_files
中元素之間的相對順序已經丟失。
這個想法是提供一個自定義解析 function 並在解析每個選項時使用計數器記錄索引。 不幸的是,解析函數沒有按照命令定義的原始順序調用。
fn get_next_atomic_int() -> usize {
static ATOMIC_COUNTER: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));
ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed)
}
fn parse_passthrough_string_ordered(arg: &str) -> (String, usize) {
(arg.to_owned(), get_next_atomic_int())
}
#[derive(Debug, StructOpt)]
#[structopt(name = "command"]
pub struct Command {
#[structopt(long = "config-file", parse(from_str = parse_passthrough_string_ordered))]
config_files: Vec<(String, usize)>,
#[structopt(short = "c", long = "config", parse(from_str = parse_passthrough_string_ordered))]
configs: Vec<(String, usize)>,
}
我可以為該選項添加別名,如下所示:
#[derive(Debug, StructOpt)]
pub struct Command {
#[structopt(long = "config", visible_alias = "config-file")]
configs: Vec<String>,
}
這種方法有兩個問題:
--config
還是--config-file
傳遞的(並不總是可以通過檢查值來確定值是如何傳遞的)。另一個想法是附加多個structopt
指令,以便兩個選項使用相同的底層向量。 不幸的是,它不起作用 - structopt
只使用最后一個指令。 就像是:
#[derive(Debug)]
enum Config {
File(String),
Literal(String),
}
fn parse_config_literal(arg: &str) -> Config {
Config::Literal(arg.to_owned())
}
fn parse_config_file(arg: &str) -> Config {
Config::File(arg.to_owned())
}
#[derive(Debug, StructOpt)]
#[structopt(name = "example")]
struct Opt {
#[structopt(long = "--config-file", parse(from_str = parse_config_file))]
#[structopt(short = "-c", long = "--config", parse(from_str = parse_config_literal))]
options: Vec<Config>,
}
我可以嘗試通過搜索解析值來恢復原始順序。 但這意味着我將不得不復制相當多的解析邏輯(例如,需要支持傳遞--config=X
、 --config X
,需要處理X
作為另一個選項的輸入,等等)。
我寧願有辦法可靠地獲得原件,而不是丟失訂單並嘗試以可能脆弱的方式恢復它。
正如@TeXitoi所述,我錯過了ArgMatches::indices_of()
function ,它為我們提供了所需的信息。
use structopt::StructOpt;
#[derive(Debug)]
enum Config {
File(String),
Literal(String),
}
fn parse_config_literal(arg: &str) -> Config {
Config::Literal(arg.to_owned())
}
fn parse_config_file(arg: &str) -> Config {
Config::File(arg.to_owned())
}
#[derive(Debug, StructOpt)]
#[structopt(name = "example")]
struct Opt {
#[structopt(short = "c", long = "config", parse(from_str = parse_config_literal))]
configs: Vec<Config>,
#[structopt(long = "config-file", parse(from_str = parse_config_file))]
config_files: Vec<Config>,
}
fn with_indices<'a, I: IntoIterator + 'a>(
collection: I,
name: &str,
matches: &'a structopt::clap::ArgMatches,
) -> impl Iterator<Item = (usize, I::Item)> + 'a {
matches
.indices_of(name)
.into_iter()
.flatten()
.zip(collection)
}
fn main() {
let args = vec!["example", "--config", "A", "--config-file", "B", "--config", "C", "--config-file", "D"];
let clap = Opt::clap();
let matches = clap.get_matches_from(args);
let opt = Opt::from_clap(&matches);
println!("configs:");
for (i, c) in with_indices(&opt.configs, "configs", &matches) {
println!("{}: {:#?}", i, c);
}
println!("\nconfig-files:");
for (i, c) in with_indices(&opt.config_files, "config-files", &matches) {
println!("{}: {:#?}", i, c);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.