簡體   English   中英

使用 clap 和 structopt 獲取不同命令行選項的相對順序

[英]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]但是configsconfig_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM